]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Version 15, March 2013
authoractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 10:46:36 +0000 (11:46 +0100)
committeractivityworkshop <mail@activityworkshop.net>
Sun, 15 Feb 2015 10:46:36 +0000 (11:46 +0100)
130 files changed:
tim/prune/App.java
tim/prune/FileDropHandler.java [new file with mode: 0644]
tim/prune/FunctionLibrary.java
tim/prune/GpsPrune.java
tim/prune/config/Config.java
tim/prune/correlate/MediaPreviewTableModel.java
tim/prune/data/Altitude.java
tim/prune/data/AltitudeRange.java
tim/prune/data/Checker.java
tim/prune/data/Coordinate.java
tim/prune/data/DataPoint.java
tim/prune/data/DoubleRange.java
tim/prune/data/MediaList.java
tim/prune/data/MediaObject.java
tim/prune/data/NumberUtils.java
tim/prune/data/PointCreateOptions.java [new file with mode: 0644]
tim/prune/data/PointScaler.java
tim/prune/data/RangeStats.java [new file with mode: 0644]
tim/prune/data/Selection.java
tim/prune/data/Speed.java [new file with mode: 0644]
tim/prune/data/SpeedCalculator.java
tim/prune/data/Timestamp.java
tim/prune/data/Track.java
tim/prune/data/TrackExtents.java [new file with mode: 0644]
tim/prune/data/Unit.java
tim/prune/data/UnitSet.java
tim/prune/data/UnitSetLibrary.java
tim/prune/function/AboutScreen.java
tim/prune/function/AddAltitudeOffset.java
tim/prune/function/AddMapSourceDialog.java
tim/prune/function/DownloadOsmFunction.java
tim/prune/function/Export3dFunction.java
tim/prune/function/FullRangeDetails.java
tim/prune/function/GetWikipediaFunction.java
tim/prune/function/PasteCoordinates.java
tim/prune/function/SearchWikipediaNames.java
tim/prune/function/SetKmzImageSize.java [deleted file]
tim/prune/function/SetLanguage.java
tim/prune/function/edit/EditFieldsTableModel.java
tim/prune/function/edit/PointEditor.java
tim/prune/function/edit/PointNameEditor.java
tim/prune/function/estimate/EstimateTime.java [new file with mode: 0644]
tim/prune/function/estimate/EstimationParameters.java [new file with mode: 0644]
tim/prune/function/estimate/LearnParameters.java [new file with mode: 0644]
tim/prune/function/estimate/ParametersPanel.java [new file with mode: 0644]
tim/prune/function/estimate/jama/Maths.java [new file with mode: 0644]
tim/prune/function/estimate/jama/Matrix.java [new file with mode: 0644]
tim/prune/function/estimate/jama/QRDecomposition.java [new file with mode: 0644]
tim/prune/function/gpsies/GenericDownloaderFunction.java
tim/prune/function/gpsies/TrackListModel.java
tim/prune/function/gpsies/UploadGpsiesFunction.java
tim/prune/function/srtm/LookupSrtmFunction.java
tim/prune/function/srtm/TileFinder.java
tim/prune/gui/DecimalNumberField.java [new file with mode: 0644]
tim/prune/gui/DetailsDisplay.java
tim/prune/gui/DisplayUtils.java
tim/prune/gui/GuiGridLayout.java
tim/prune/gui/IconManager.java
tim/prune/gui/MenuManager.java
tim/prune/gui/ProgressDialog.java [new file with mode: 0644]
tim/prune/gui/StatusIcon.java [new file with mode: 0644]
tim/prune/gui/WholeNumberField.java
tim/prune/gui/WizardLayout.java [new file with mode: 0644]
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/entry_invalid.gif [new file with mode: 0644]
tim/prune/gui/images/entry_none.gif [new file with mode: 0644]
tim/prune/gui/images/entry_valid.gif [new file with mode: 0644]
tim/prune/gui/map/MapCanvas.java
tim/prune/gui/map/MapUtils.java
tim/prune/gui/profile/AltitudeData.java
tim/prune/gui/profile/ArbitraryData.java [new file with mode: 0644]
tim/prune/gui/profile/ProfileChart.java
tim/prune/lang/prune-texts_af.properties
tim/prune/lang/prune-texts_cz.properties
tim/prune/lang/prune-texts_da.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_en_US.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_ru.properties
tim/prune/lang/prune-texts_tr.properties
tim/prune/lang/prune-texts_zh.properties
tim/prune/load/BabelFileFormats.java
tim/prune/load/BabelLoadFromFile.java
tim/prune/load/BabelLoadFromGps.java
tim/prune/load/BabelLoader.java
tim/prune/load/ComponentHider.java [new file with mode: 0644]
tim/prune/load/FieldGuesser.java
tim/prune/load/FileCacher.java
tim/prune/load/FileLoader.java
tim/prune/load/JpegLoader.java
tim/prune/load/MediaLoadProgressDialog.java
tim/prune/load/NmeaFileLoader.java
tim/prune/load/TextFileLoader.java
tim/prune/load/babel/AddFilterDialog.java [new file with mode: 0644]
tim/prune/load/babel/BabelFilterPanel.java [new file with mode: 0644]
tim/prune/load/babel/DiscardFilter.java [new file with mode: 0644]
tim/prune/load/babel/DistanceFilter.java [new file with mode: 0644]
tim/prune/load/babel/FilterDefinition.java [new file with mode: 0644]
tim/prune/load/babel/InterpolateFilter.java [new file with mode: 0644]
tim/prune/load/babel/SimplifyFilter.java [new file with mode: 0644]
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 [new file with mode: 0644]
tim/prune/save/ExifSaver.java
tim/prune/save/FileSaver.java
tim/prune/save/GpxExporter.java
tim/prune/save/GroutedImage.java [new file with mode: 0644]
tim/prune/save/ImageExporter.java [new file with mode: 0644]
tim/prune/save/ImagePreviewPanel.java [new file with mode: 0644]
tim/prune/save/KmlExporter.java
tim/prune/save/MapGrouter.java [new file with mode: 0644]
tim/prune/save/PovExporter.java
tim/prune/save/SvgExporter.java
tim/prune/threedee/Java3DWindow.java
tim/prune/threedee/LineDialog.java [deleted file]
tim/prune/threedee/ThreeDModel.java
tim/prune/undo/UndoDeleteRange.java

index 205d88ae11c74165c23bd6aa7ef1fab275af4485..1f223265b0aa929e9099a6d7aba71ea5b0a49a0d 100644 (file)
@@ -10,7 +10,6 @@ import javax.swing.JFrame;
 import javax.swing.JOptionPane;
 
 import tim.prune.config.Config;
 import javax.swing.JOptionPane;
 
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.Checker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Checker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
@@ -18,11 +17,13 @@ import tim.prune.data.LatLonRectangle;
 import tim.prune.data.NumberUtils;
 import tim.prune.data.Photo;
 import tim.prune.data.PhotoList;
 import tim.prune.data.NumberUtils;
 import tim.prune.data.Photo;
 import tim.prune.data.PhotoList;
+import tim.prune.data.PointCreateOptions;
 import tim.prune.data.RecentFile;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
 import tim.prune.data.RecentFile;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
+import tim.prune.data.Unit;
 import tim.prune.function.AsyncMediaLoader;
 import tim.prune.function.SaveConfig;
 import tim.prune.function.SelectTracksFunction;
 import tim.prune.function.AsyncMediaLoader;
 import tim.prune.function.SaveConfig;
 import tim.prune.function.SelectTracksFunction;
@@ -107,7 +108,7 @@ public class App
        public boolean hasDataUnsaved()
        {
                return (_undoStack.size() > _lastSavePosition
        public boolean hasDataUnsaved()
        {
                return (_undoStack.size() > _lastSavePosition
-                       && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().getNumPhotos() > 0));
+                       && (_track.getNumPoints() > 0 || _trackInfo.getPhotoList().hasModifiedMedia()));
        }
 
        /**
        }
 
        /**
@@ -403,12 +404,12 @@ public class App
        /**
         * Complete the add altitude offset function with the specified offset
         * @param inOffset altitude offset to add as String
        /**
         * Complete the add altitude offset function with the specified offset
         * @param inOffset altitude offset to add as String
-        * @param inFormat altitude format of offset (eg Feet, Metres)
+        * @param inUnit altitude units of offset (eg Feet, Metres)
         */
         */
-       public void finishAddAltitudeOffset(String inOffset, Altitude.Format inFormat)
+       public void finishAddAltitudeOffset(String inOffset, Unit inUnit)
        {
                // Sanity check
        {
                // Sanity check
-               if (inOffset == null || inOffset.equals("") || inFormat==Altitude.Format.NO_FORMAT) {
+               if (inOffset == null || inOffset.equals("") || inUnit == null) {
                        return;
                }
                // Construct undo information
                        return;
                }
                // Construct undo information
@@ -421,7 +422,7 @@ public class App
                // Decimal offset given
                try {
                        double offsetd = Double.parseDouble(inOffset);
                // Decimal offset given
                try {
                        double offsetd = Double.parseDouble(inOffset);
-                       success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inFormat, numDecimals);
+                       success = _trackInfo.getTrack().addAltitudeOffset(selStart, selEnd, offsetd, inUnit, numDecimals);
                }
                catch (NumberFormatException nfe) {}
                if (success)
                }
                catch (NumberFormatException nfe) {}
                if (success)
@@ -568,15 +569,14 @@ public class App
         * Receive loaded data and determine whether to filter on tracks or not
         * @param inFieldArray array of fields
         * @param inDataArray array of data
         * Receive loaded data and determine whether to filter on tracks or not
         * @param inFieldArray array of fields
         * @param inDataArray array of data
-        * @param inAltFormat altitude format
         * @param inSourceInfo information about the source of the data
         * @param inTrackNameList information about the track names
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
         * @param inSourceInfo information about the source of the data
         * @param inTrackNameList information about the track names
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
-               Altitude.Format inAltFormat, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
+               SourceInfo inSourceInfo, TrackNameList inTrackNameList)
        {
                // no link array given
        {
                // no link array given
-               informDataLoaded(inFieldArray, inDataArray, inAltFormat, inSourceInfo,
+               informDataLoaded(inFieldArray, inDataArray, null, inSourceInfo,
                        inTrackNameList, null);
        }
 
                        inTrackNameList, null);
        }
 
@@ -584,18 +584,33 @@ public class App
         * Receive loaded data and determine whether to filter on tracks or not
         * @param inFieldArray array of fields
         * @param inDataArray array of data
         * Receive loaded data and determine whether to filter on tracks or not
         * @param inFieldArray array of fields
         * @param inDataArray array of data
-        * @param inAltFormat altitude format
+        * @param inOptions creation options such as units
         * @param inSourceInfo information about the source of the data
         * @param inTrackNameList information about the track names
         * @param inSourceInfo information about the source of the data
         * @param inTrackNameList information about the track names
-        * @param inLinkInfo links to photo/audio clips
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
         */
        public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray,
-               Altitude.Format inAltFormat, SourceInfo inSourceInfo,
-               TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
+               PointCreateOptions inOptions, SourceInfo inSourceInfo, TrackNameList inTrackNameList)
+       {
+               // no link array given
+               informDataLoaded(inFieldArray, inDataArray, inOptions, inSourceInfo,
+                       inTrackNameList, null);
+       }
+
+       /**
+        * Receive loaded data and determine whether to filter on tracks or not
+        * @param inFieldArray array of fields
+        * @param inDataArray array of data
+        * @param inOptions creation options such as units
+        * @param inSourceInfo information about the source of the data
+        * @param inTrackNameList information about the track names
+        * @param inLinkInfo links to photo/audio clips
+        */
+       public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, PointCreateOptions inOptions,
+               SourceInfo inSourceInfo, TrackNameList inTrackNameList, MediaLinkInfo inLinkInfo)
        {
                // Check whether loaded array can be properly parsed into a Track
                Track loadedTrack = new Track();
        {
                // Check whether loaded array can be properly parsed into a Track
                Track loadedTrack = new Track();
-               loadedTrack.load(inFieldArray, inDataArray, inAltFormat);
+               loadedTrack.load(inFieldArray, inDataArray, inOptions);
                if (loadedTrack.getNumPoints() <= 0)
                {
                        showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
                if (loadedTrack.getNumPoints() <= 0)
                {
                        showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
diff --git a/tim/prune/FileDropHandler.java b/tim/prune/FileDropHandler.java
new file mode 100644 (file)
index 0000000..2c76385
--- /dev/null
@@ -0,0 +1,158 @@
+package tim.prune;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+
+import javax.swing.TransferHandler;
+
+/**
+ * Class to listen for drag/drop events and react to dropping files on to the application
+ */
+public class FileDropHandler extends TransferHandler
+{
+       /** App object for passing results back to */
+       private App _app = null;
+
+       /** Fixed flavour in case the java file list flavour isn't available */
+       private static DataFlavor _uriListFlavour = null;
+
+       /** Static block to initialise the list flavour */
+       static
+       {
+               try {_uriListFlavour = new DataFlavor("text/uri-list;class=java.lang.String");
+               } catch (ClassNotFoundException nfe) {}
+       }
+
+
+       /**
+        * Constructor
+        * @param inApp App object to pass results of drop back to
+        */
+       public FileDropHandler(App inApp)
+       {
+               _app = inApp;
+       }
+
+       /**
+        * Check if the object being dragged can be accepted
+        * @param inSupport object to check
+        */
+       public boolean canImport(TransferSupport inSupport)
+       {
+               boolean retval = inSupport.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
+                       || inSupport.isDataFlavorSupported(_uriListFlavour);
+               // Modify icon to show a copy, not a move (+ icon on cursor)
+               if (retval) {
+                       inSupport.setDropAction(COPY);
+               }
+               return retval;
+       }
+
+       /**
+        * Accept the incoming data and pass it on to the App
+        * @param inSupport contents of drop
+        */
+       public boolean importData(TransferSupport inSupport)
+       {
+               if (!canImport(inSupport)) {return false;} // not allowed
+
+               boolean success = false;
+               ArrayList<File> dataFiles = new ArrayList<File>();
+
+               // Try a java file list flavour first
+               try
+               {
+                       @SuppressWarnings("unchecked")
+                       java.util.List<File> fileList = (java.util.List<File>)
+                       inSupport.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
+                       success = true;
+
+                       for (File f : fileList)
+                       {
+                               if (f != null && f.exists())
+                               {
+                                       if (f.isDirectory()) {
+                                               addDirectoryToList(f, dataFiles);
+                                       }
+                                       else if (f.isFile()) {
+                                               dataFiles.add(f);
+                                       }
+                               }
+                       }
+               } catch (Exception e) {}  // exception caught, probably missing a javafilelist flavour - just continue
+
+               // If that didn't work, try a list of strings instead
+               if (!success)
+               {
+                       try
+                       {
+                               String pathList = inSupport.getTransferable().getTransferData(_uriListFlavour).toString();
+                               success = true;
+
+                               for (String s : pathList.split("[\n\r]+"))
+                               {
+                                       if (s != null && !s.equals(""))
+                                       {
+                                               File f = new File(new URI(s));
+                                               if (f.exists())
+                                               {
+                                                       if (f.isDirectory()) {
+                                                               addDirectoryToList(f, dataFiles);
+                                                       }
+                                                       else if (f.isFile()) {
+                                                               dataFiles.add(f);
+                                                       }
+                                               }
+                                       }
+                               }
+                       } catch (Exception e) {
+                               System.err.println("exception: " + e.getClass().getName() + " - " + e.getMessage());
+                               return false;
+                       }
+               }
+
+               // Pass files back to app
+               if (!dataFiles.isEmpty()) {
+                       _app.loadDataFiles(dataFiles);
+               }
+               return true;
+       }
+
+
+       /**
+        * Recursively-called method to add files from the given directory (and its subdirectories)
+        * to the given list of files
+        * @param inDir directory to add
+        * @param inList file list to append to
+        */
+       private void addDirectoryToList(File inDir, ArrayList<File> inList)
+       {
+               if (inDir != null && inDir.exists() && inDir.canRead() && inDir.isDirectory() && inList != null)
+               {
+                       for (String path : inDir.list())
+                       {
+                               if (path != null)
+                               {
+                                       File f = new File(inDir, path);
+                                       if (f.exists() && f.canRead())
+                                       {
+                                               if (f.isFile())
+                                               {
+                                                       // Add the found file to the list (if it's not in there already)
+                                                       if (!inList.contains(f)) {
+                                                               inList.add(f);
+                                                       }
+                                               }
+                                               else if (f.isDirectory())
+                                               {
+                                                       // Recursively add the files from subdirectories
+                                                       addDirectoryToList(f, inList);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+}
index f06dbe457983ad723c8f00ee9f3c8b797517b656..53d8563cc33587d4303d945ff2d51fd44cca57fa 100644 (file)
@@ -7,6 +7,8 @@ import tim.prune.function.charts.Charter;
 import tim.prune.function.compress.CompressTrackFunction;
 import tim.prune.function.distance.DistanceFunction;
 import tim.prune.function.edit.PointNameEditor;
 import tim.prune.function.compress.CompressTrackFunction;
 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.srtm.LookupSrtmFunction;
 import tim.prune.function.gpsies.GetGpsiesFunction;
 import tim.prune.function.gpsies.UploadGpsiesFunction;
 import tim.prune.function.srtm.LookupSrtmFunction;
@@ -15,6 +17,7 @@ import tim.prune.load.BabelLoadFromFile;
 import tim.prune.load.BabelLoadFromGps;
 import tim.prune.save.GpsSaver;
 import tim.prune.save.GpxExporter;
 import tim.prune.load.BabelLoadFromGps;
 import tim.prune.save.GpsSaver;
 import tim.prune.save.GpxExporter;
+import tim.prune.save.ImageExporter;
 import tim.prune.save.KmlExporter;
 import tim.prune.save.PovExporter;
 import tim.prune.save.SvgExporter;
 import tim.prune.save.KmlExporter;
 import tim.prune.save.PovExporter;
 import tim.prune.save.SvgExporter;
@@ -28,6 +31,7 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_KMLEXPORT = null;
        public static PovExporter FUNCTION_POVEXPORT     = null;
        public static SvgExporter FUNCTION_SVGEXPORT     = null;
        public static GenericFunction FUNCTION_KMLEXPORT = null;
        public static PovExporter FUNCTION_POVEXPORT     = null;
        public static SvgExporter FUNCTION_SVGEXPORT     = null;
+       public static GenericFunction FUNCTION_IMAGEEXPORT = null;
        public static GenericFunction FUNCTION_GPSLOAD  = null;
        public static GenericFunction FUNCTION_GPSSAVE  = null;
        public static GenericFunction FUNCTION_IMPORTBABEL = null;
        public static GenericFunction FUNCTION_GPSLOAD  = null;
        public static GenericFunction FUNCTION_GPSSAVE  = null;
        public static GenericFunction FUNCTION_IMPORTBABEL = null;
@@ -63,6 +67,8 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_3D     = null;
        public static GenericFunction FUNCTION_DISTANCES  = null;
        public static GenericFunction FUNCTION_FULL_RANGE_DETAILS = null;
        public static GenericFunction FUNCTION_3D     = null;
        public static GenericFunction FUNCTION_DISTANCES  = null;
        public static GenericFunction FUNCTION_FULL_RANGE_DETAILS = null;
+       public static GenericFunction FUNCTION_ESTIMATE_TIME = null;
+       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_LOAD_AUDIO = null;
        public static GenericFunction FUNCTION_GET_GPSIES = null;
        public static GenericFunction FUNCTION_UPLOAD_GPSIES = null;
        public static GenericFunction FUNCTION_LOAD_AUDIO = null;
@@ -73,7 +79,6 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_SET_MAP_BG = null;
        public static GenericFunction FUNCTION_SET_DISK_CACHE = null;
        public static GenericFunction FUNCTION_SET_PATHS  = null;
        public static GenericFunction FUNCTION_SET_MAP_BG = null;
        public static GenericFunction FUNCTION_SET_DISK_CACHE = null;
        public static GenericFunction FUNCTION_SET_PATHS  = null;
-       public static GenericFunction FUNCTION_SET_KMZ_IMAGE_SIZE = null;
        public static GenericFunction FUNCTION_SET_COLOURS = null;
        public static GenericFunction FUNCTION_SET_LINE_WIDTH = null;
        public static GenericFunction FUNCTION_SET_LANGUAGE = null;
        public static GenericFunction FUNCTION_SET_COLOURS = null;
        public static GenericFunction FUNCTION_SET_LINE_WIDTH = null;
        public static GenericFunction FUNCTION_SET_LANGUAGE = null;
@@ -93,6 +98,7 @@ public abstract class FunctionLibrary
                FUNCTION_KMLEXPORT = new KmlExporter(inApp);
                FUNCTION_POVEXPORT = new PovExporter(inApp);
                FUNCTION_SVGEXPORT = new SvgExporter(inApp);
                FUNCTION_KMLEXPORT = new KmlExporter(inApp);
                FUNCTION_POVEXPORT = new PovExporter(inApp);
                FUNCTION_SVGEXPORT = new SvgExporter(inApp);
+               FUNCTION_IMAGEEXPORT = new ImageExporter(inApp);
                FUNCTION_GPSLOAD   = new BabelLoadFromGps(inApp);
                FUNCTION_GPSSAVE   = new GpsSaver(inApp);
                FUNCTION_IMPORTBABEL = new BabelLoadFromFile(inApp);
                FUNCTION_GPSLOAD   = new BabelLoadFromGps(inApp);
                FUNCTION_GPSSAVE   = new GpsSaver(inApp);
                FUNCTION_IMPORTBABEL = new BabelLoadFromFile(inApp);
@@ -127,6 +133,8 @@ public abstract class FunctionLibrary
                FUNCTION_3D     = new ShowThreeDFunction(inApp);
                FUNCTION_DISTANCES = new DistanceFunction(inApp);
                FUNCTION_FULL_RANGE_DETAILS = new FullRangeDetails(inApp);
                FUNCTION_3D     = new ShowThreeDFunction(inApp);
                FUNCTION_DISTANCES = new DistanceFunction(inApp);
                FUNCTION_FULL_RANGE_DETAILS = new FullRangeDetails(inApp);
+               FUNCTION_ESTIMATE_TIME = new EstimateTime(inApp);
+               FUNCTION_LEARN_ESTIMATION_PARAMS = new LearnParameters(inApp);
                FUNCTION_GET_GPSIES = new GetGpsiesFunction(inApp);
                FUNCTION_UPLOAD_GPSIES = new UploadGpsiesFunction(inApp);
                FUNCTION_LOAD_AUDIO = new AudioLoader(inApp);
                FUNCTION_GET_GPSIES = new GetGpsiesFunction(inApp);
                FUNCTION_UPLOAD_GPSIES = new UploadGpsiesFunction(inApp);
                FUNCTION_LOAD_AUDIO = new AudioLoader(inApp);
@@ -138,7 +146,6 @@ public abstract class FunctionLibrary
                FUNCTION_SET_MAP_BG = new SetMapBgFunction(inApp);
                FUNCTION_SET_DISK_CACHE = new DiskCacheConfig(inApp);
                FUNCTION_SET_PATHS = new SetPathsFunction(inApp);
                FUNCTION_SET_MAP_BG = new SetMapBgFunction(inApp);
                FUNCTION_SET_DISK_CACHE = new DiskCacheConfig(inApp);
                FUNCTION_SET_PATHS = new SetPathsFunction(inApp);
-               FUNCTION_SET_KMZ_IMAGE_SIZE = new SetKmzImageSize(inApp);
                FUNCTION_SET_COLOURS = new SetColours(inApp);
                FUNCTION_SET_LINE_WIDTH = new SetLineWidth(inApp);
                FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
                FUNCTION_SET_COLOURS = new SetColours(inApp);
                FUNCTION_SET_LINE_WIDTH = new SetLineWidth(inApp);
                FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
index 70e1c8e7858669816843165d5dfd386b006b62ed..3214baca31fc3bfd7cdfd6a17a40375c4ac1c4b2 100644 (file)
@@ -35,9 +35,9 @@ import tim.prune.gui.profile.ProfileChart;
 public class GpsPrune
 {
        /** Version number of application, used in about screen and for version check */
 public class GpsPrune
 {
        /** Version number of application, used in about screen and for version check */
-       public static final String VERSION_NUMBER = "14.1";
+       public static final String VERSION_NUMBER = "15";
        /** Build number, just used for about screen */
        /** Build number, just used for about screen */
-       public static final String BUILD_NUMBER = "265a";
+       public static final String BUILD_NUMBER = "283";
        /** Static reference to App object */
        private static App APP = null;
 
        /** Static reference to App object */
        private static App APP = null;
 
@@ -98,8 +98,9 @@ public class GpsPrune
                                }
                        }
                }
                                }
                        }
                }
-               if (showUsage) {
-                       System.out.println("Possible parameters:"
+               if (showUsage)
+               {
+                       System.out.println("GpsPrune - a tool for editing GPS data.\nPossible parameters:"
                                + "\n   --configfile=<file> used to specify a configuration file"
                                + "\n   --lang=<code>       used to specify language code such as DE"
                                + "\n   --langfile=<file>   used to specify an alternative language file\n");
                                + "\n   --configfile=<file> used to specify a configuration file"
                                + "\n   --lang=<code>       used to specify language code such as DE"
                                + "\n   --langfile=<file>   used to specify an alternative language file\n");
@@ -233,6 +234,9 @@ public class GpsPrune
                }
                catch (Exception e) {} // ignore
 
                }
                catch (Exception e) {} // ignore
 
+               // Set up drag-and-drop handler to accept dropped files
+               frame.setTransferHandler(new FileDropHandler(APP));
+
                // finish off and display frame
                frame.pack();
                frame.setSize(650, 450);
                // finish off and display frame
                frame.pack();
                frame.setSize(650, 450);
index 5a95bebdebc62d52c6da4f52ec7391026781c206..385a5d9f12dc64d387711cf54abc4fbbb5644633 100644 (file)
@@ -42,6 +42,8 @@ public abstract class Config
        public static final String KEY_GPS_DEVICE = "prune.gpsdevice";
        /** Key for GPS format */
        public static final String KEY_GPS_FORMAT = "prune.gpsformat";
        public static final String KEY_GPS_DEVICE = "prune.gpsdevice";
        /** Key for GPS format */
        public static final String KEY_GPS_FORMAT = "prune.gpsformat";
+       /** Key for GPSBabel filter string */
+       public static final String KEY_GPSBABEL_FILTER = "prune.gpsbabelfilter";
        /** Key for Povray font */
        public static final String KEY_POVRAY_FONT = "prune.povrayfont";
        /** Key for the selected unit set */
        /** Key for Povray font */
        public static final String KEY_POVRAY_FONT = "prune.povrayfont";
        /** Key for the selected unit set */
@@ -59,9 +61,7 @@ public abstract class Config
        /** Key for working online flag */
        public static final String KEY_ONLINE_MODE = "prune.onlinemode";
        /** Key for width of thumbnails in kmz */
        /** Key for working online flag */
        public static final String KEY_ONLINE_MODE = "prune.onlinemode";
        /** Key for width of thumbnails in kmz */
-       public static final String KEY_KMZ_IMAGE_WIDTH = "prune.kmzimagewidth";
-       /** Key for height of thumbnails in kmz */
-       public static final String KEY_KMZ_IMAGE_HEIGHT = "prune.kmzimageheight";
+       public static final String KEY_KMZ_IMAGE_SIZE = "prune.kmzimagewidth";
        /** Key for gpsbabel path */
        public static final String KEY_GPSBABEL_PATH = "prune.gpsbabelpath";
        /** Key for gnuplot path */
        /** Key for gpsbabel path */
        public static final String KEY_GPSBABEL_PATH = "prune.gpsbabelpath";
        /** Key for gnuplot path */
@@ -78,6 +78,8 @@ public abstract class Config
        public static final String KEY_AUTOSAVE_SETTINGS = "prune.autosavesettings";
        /** Key for recently used files */
        public static final String KEY_RECENT_FILES = "prune.recentfiles";
        public static final String KEY_AUTOSAVE_SETTINGS = "prune.autosavesettings";
        /** Key for recently used files */
        public static final String KEY_RECENT_FILES = "prune.recentfiles";
+       /** Key for estimation parameters */
+       public static final String KEY_ESTIMATION_PARAMS = "prune.estimationparams";
 
 
        /** Initialise the default properties */
 
 
        /** Initialise the default properties */
@@ -162,8 +164,7 @@ public abstract class Config
                props.put(KEY_EXIFTOOL_PATH, "exiftool");
                props.put(KEY_GNUPLOT_PATH, "gnuplot");
                props.put(KEY_GPSBABEL_PATH, "gpsbabel");
                props.put(KEY_EXIFTOOL_PATH, "exiftool");
                props.put(KEY_GNUPLOT_PATH, "gnuplot");
                props.put(KEY_GPSBABEL_PATH, "gpsbabel");
-               props.put(KEY_KMZ_IMAGE_WIDTH, "240");
-               props.put(KEY_KMZ_IMAGE_HEIGHT, "240");
+               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
                return props;
                props.put(KEY_AUTOSAVE_SETTINGS, "0"); // autosave false by default
                props.put(KEY_UNITSET_KEY, "unitset.kilometres"); // metric by default
                return props;
index 3d6efa8196c1ca57cf47aced06d83b211577c5f8..e562ff9f4ff32fc764f70bbec9f6ea09207caa32 100644 (file)
@@ -1,11 +1,12 @@
 package tim.prune.correlate;
 
 package tim.prune.correlate;
 
-import java.text.NumberFormat;
 import java.util.ArrayList;
 import javax.swing.table.AbstractTableModel;
 import java.util.ArrayList;
 import javax.swing.table.AbstractTableModel;
+
 import tim.prune.I18nManager;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
 import tim.prune.I18nManager;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.DisplayUtils;
 
 /**
  * Class to act as the table model for the correlation preview table
 
 /**
  * Class to act as the table model for the correlation preview table
@@ -18,16 +19,7 @@ public class MediaPreviewTableModel extends AbstractTableModel
        private ArrayList<MediaPreviewTableRow> _list = new ArrayList<MediaPreviewTableRow>();
        /** Distance units */
        private Unit _distanceUnits = UnitSetLibrary.UNITS_KILOMETRES;
        private ArrayList<MediaPreviewTableRow> _list = new ArrayList<MediaPreviewTableRow>();
        /** Distance units */
        private Unit _distanceUnits = UnitSetLibrary.UNITS_KILOMETRES;
-       /** Number formatter */
-       private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
-
 
 
-       /** Static block to initialise the one d.p. formatter */
-       static
-       {
-               FORMAT_ONE_DP.setMaximumFractionDigits(1);
-               FORMAT_ONE_DP.setMinimumFractionDigits(1);
-       }
 
        /**
         * Constructor
 
        /**
         * Constructor
@@ -103,7 +95,7 @@ public class MediaPreviewTableModel extends AbstractTableModel
                }
                else if (inColumnIndex == 3) {
                        if (row.getPointPair().isValid()) {
                }
                else if (inColumnIndex == 3) {
                        if (row.getPointPair().isValid()) {
-                               return FORMAT_ONE_DP.format(row.getDistance(_distanceUnits));
+                               return DisplayUtils.formatOneDp(row.getDistance(_distanceUnits));
                        }
                        return "";
                }
                        }
                        return "";
                }
index ac71d759cb4a45a8a7af971b8d6adc830010bf83..7d9c21cb6c414a5f4d16503e1f6bce0dbf215a73 100644 (file)
@@ -7,41 +7,26 @@ public class Altitude
 {
        private boolean _valid = false;
        private int _value = 0;
 {
        private boolean _valid = false;
        private int _value = 0;
-       private Format _format = Format.NO_FORMAT;
+       private Unit _unit = null;
        private String _stringValue = null;
 
        private String _stringValue = null;
 
-       /** Altitude formats */
-       public enum Format {
-               /** No format */
-               NO_FORMAT,
-               /** Metres */
-               METRES,
-               /** Feet */
-               FEET
-       }
-
-       /** Constants for conversion */
-       private static final double CONVERT_METRES_TO_FEET = UnitSetLibrary.UNITS_FEET.getMultFactorFromStd();
-       private static final double CONVERT_FEET_TO_METRES = 1 / CONVERT_METRES_TO_FEET;
-
-       /** Constant for no altitude value */
-       public static final Altitude NONE = new Altitude(null, Format.NO_FORMAT);
-
+       /** Constant to use for a lack of altitude */
+       public static final Altitude NONE = new Altitude(null, null);
 
        /**
         * Constructor using String
         * @param inString string to parse
 
        /**
         * Constructor using String
         * @param inString string to parse
-        * @param inFormat format of altitude, either metres or feet
+        * @param inUnit of altitude, either metres or feet
         */
         */
-       public Altitude(String inString, Format inFormat)
+       public Altitude(String inString, Unit inUnit)
        {
        {
+               _unit = inUnit;
                if (inString != null && !inString.equals(""))
                {
                        try
                        {
                                _stringValue = inString;
                                _value = (int) Double.parseDouble(inString.trim());
                if (inString != null && !inString.equals(""))
                {
                        try
                        {
                                _stringValue = inString;
                                _value = (int) Double.parseDouble(inString.trim());
-                               _format = inFormat;
                                _valid = true;
                        }
                        catch (NumberFormatException nfe) {}
                                _valid = true;
                        }
                        catch (NumberFormatException nfe) {}
@@ -52,13 +37,14 @@ public class Altitude
        /**
         * Constructor with int value
         * @param inValue int value of altitude
        /**
         * Constructor with int value
         * @param inValue int value of altitude
-        * @param inFormat format of altitude, either metres or feet
+        * @param inUnit unit of altitude, either metres or feet
         */
         */
-       public Altitude(int inValue, Format inFormat)
+       public Altitude(int inValue, Unit inUnit)
        {
                _value = inValue;
        {
                _value = inValue;
-               _format = inFormat;
+               _unit = inUnit;
                _valid = true;
                _valid = true;
+               _stringValue = "" + inValue;
        }
 
        /**
        }
 
        /**
@@ -66,7 +52,7 @@ public class Altitude
         */
        public Altitude clone()
        {
         */
        public Altitude clone()
        {
-               return new Altitude(_stringValue, _format);
+               return new Altitude(_stringValue, _unit);
        }
 
        /**
        }
 
        /**
@@ -77,7 +63,7 @@ public class Altitude
        {
                _stringValue = inClone._stringValue;
                _value = inClone._value;
        {
                _stringValue = inClone._stringValue;
                _value = inClone._value;
-               _format = inClone._format;
+               _unit = inClone._unit;
                _valid = inClone._valid;
        }
 
                _valid = inClone._valid;
        }
 
@@ -104,33 +90,18 @@ public class Altitude
         */
        public int getValue(Unit inAltUnit)
        {
         */
        public int getValue(Unit inAltUnit)
        {
+               if (inAltUnit == null) {
+                       return getValue();
+               }
                return (int) (getMetricValue() * inAltUnit.getMultFactorFromStd());
        }
 
        /**
                return (int) (getMetricValue() * inAltUnit.getMultFactorFromStd());
        }
 
        /**
-        * @return format of number
+        * @return unit of number
         */
         */
-       public Format getFormat()
+       public Unit getUnit()
        {
        {
-               return _format;
-       }
-
-
-       /**
-        * Get the altitude value in the specified format
-        * @param inFormat desired format, either FORMAT_METRES or FORMAT_FEET
-        * @return value as an int
-        */
-       public int getValue(Format inFormat)
-       {
-               // Note possible rounding errors here if converting to/from units
-               if (inFormat == _format)
-                       return _value;
-               if (inFormat == Format.METRES)
-                       return (int) (_value * CONVERT_FEET_TO_METRES);
-               if (inFormat == Format.FEET)
-                       return (int) (_value * CONVERT_METRES_TO_FEET);
-               return _value;
+               return _unit;
        }
 
        /**
        }
 
        /**
@@ -138,26 +109,26 @@ public class Altitude
         */
        public double getMetricValue()
        {
         */
        public double getMetricValue()
        {
-               if (_format == Format.FEET) {
-                       return _value / UnitSetLibrary.UNITS_FEET.getMultFactorFromStd();
+               if (_unit == UnitSetLibrary.UNITS_METRES || _unit == null) {
+                       return _value;
                }
                }
-               return _value;
+               return _value / _unit.getMultFactorFromStd();
        }
 
        /**
         * Get a string version of the value
        }
 
        /**
         * Get a string version of the value
-        * @param inFormat specified format
+        * @param inUnit specified unit
         * @return string value, if possible the original one
         */
         * @return string value, if possible the original one
         */
-       public String getStringValue(Format inFormat)
+       public String getStringValue(Unit inUnit)
        {
                if (!_valid) {return "";}
                // Return string value if the same format or "no format" was requested
        {
                if (!_valid) {return "";}
                // Return string value if the same format or "no format" was requested
-               if ((inFormat == _format || inFormat == Format.NO_FORMAT)
+               if ((inUnit == _unit || inUnit == null)
                 && _stringValue != null && !_stringValue.equals("")) {
                        return _stringValue;
                }
                 && _stringValue != null && !_stringValue.equals("")) {
                        return _stringValue;
                }
-               return "" + getValue(inFormat);
+               return "" + getValue(inUnit);
        }
 
 
        }
 
 
@@ -188,39 +159,36 @@ public class Altitude
                if (inStart == null || inEnd == null || !inStart.isValid() || !inEnd.isValid())
                        return Altitude.NONE;
                // Use altitude format of first point
                if (inStart == null || inEnd == null || !inStart.isValid() || !inEnd.isValid())
                        return Altitude.NONE;
                // Use altitude format of first point
-               Format altFormat = inStart.getFormat();
+               Unit altUnit = inStart.getUnit();
                int startValue = inStart.getValue();
                int startValue = inStart.getValue();
-               int endValue = inEnd.getValue(altFormat);
+               int endValue = inEnd.getValue(altUnit);
                // interpolate between start and end
                int newValue = startValue + (int) ((endValue - startValue) * inFrac);
                // interpolate between start and end
                int newValue = startValue + (int) ((endValue - startValue) * inFrac);
-               return new Altitude(newValue, altFormat);
+               return new Altitude(newValue, altUnit);
        }
 
        /**
         * Add the given offset to the current altitude
         * @param inOffset offset as double
        }
 
        /**
         * Add the given offset to the current altitude
         * @param inOffset offset as double
-        * @param inFormat format of offset, feet or metres
+        * @param inUnit unit of offset, feet or metres
         * @param inDecimals number of decimal places
         */
         * @param inDecimals number of decimal places
         */
-       public void addOffset(double inOffset, Format inFormat, int inDecimals)
+       public void addOffset(double inOffset, Unit inUnit, int inDecimals)
        {
                // Use the maximum number of decimal places from current value and offset
                int numDecimals = NumberUtils.getDecimalPlaces(_stringValue);
                if (numDecimals < inDecimals) {numDecimals = inDecimals;}
                // Convert offset to correct units
                double offset = inOffset;
        {
                // Use the maximum number of decimal places from current value and offset
                int numDecimals = NumberUtils.getDecimalPlaces(_stringValue);
                if (numDecimals < inDecimals) {numDecimals = inDecimals;}
                // Convert offset to correct units
                double offset = inOffset;
-               if (inFormat != _format)
+               if (inUnit != _unit && inUnit != null)
                {
                {
-                       if (inFormat == Format.FEET)
-                               offset = inOffset * CONVERT_FEET_TO_METRES;
-                       else
-                               offset = inOffset * CONVERT_METRES_TO_FEET;
+                       offset = inOffset / inUnit.getMultFactorFromStd() * _unit.getMultFactorFromStd();
                }
                // FIXME: The following will fail if _stringValue is null - not sure how it can get in that state!
                if (_stringValue == null) System.err.println("*** Altitude.addOffset - how did the string value get to be null?");
                // Add the offset
                double newValue = Double.parseDouble(_stringValue.trim()) + offset;
                _value = (int) newValue;
                }
                // FIXME: The following will fail if _stringValue is null - not sure how it can get in that state!
                if (_stringValue == null) System.err.println("*** Altitude.addOffset - how did the string value get to be null?");
                // Add the offset
                double newValue = Double.parseDouble(_stringValue.trim()) + offset;
                _value = (int) newValue;
-               _stringValue = NumberUtils.formatNumber(newValue, numDecimals);
+               _stringValue = NumberUtils.formatNumberUk(newValue, numDecimals);
        }
 }
        }
 }
index b7ec647ab6456f4ad9bdef0f625d513fa28360d4..d53223e9ad09589e5ace7bb8726c8a68711ac6a0 100644 (file)
@@ -118,4 +118,12 @@ public class AltitudeRange
        {
                return (int) (_descent * inUnit.getMultFactorFromStd());
        }
        {
                return (int) (_descent * inUnit.getMultFactorFromStd());
        }
+
+       /**
+        * @return overall height gain in metres
+        */
+       public double getMetricHeightDiff()
+       {
+               return _climb - _descent;
+       }
 }
 }
index 274c3e70f27977821ce1a3a559ffa1f83033e230..085f50f88dcff01f33bff213f453953cef30f4a7 100644 (file)
@@ -18,7 +18,7 @@ public abstract class Checker
                if (inTrack == null || inTrack.getNumPoints() < 2) {return false;}
                // Check for non-even number of points
                final int numPoints = inTrack.getNumPoints();
                if (inTrack == null || inTrack.getNumPoints() < 2) {return false;}
                // Check for non-even number of points
                final int numPoints = inTrack.getNumPoints();
-               if (numPoints % 2 == 1) {return false;}
+               if (numPoints % 2 != 0) {return false;}
                // Loop through first half of track
                final int halfNum = numPoints / 2;
                for (int i=0; i<halfNum; i++)
                // Loop through first half of track
                final int halfNum = numPoints / 2;
                for (int i=0; i<halfNum; i++)
index a230e775a21424a9d1fcc06aa004d0b35e92d015..085c502b363a5c81dfe18d45ebd583a92a3360c2 100644 (file)
@@ -84,7 +84,7 @@ public abstract class Coordinate
 
                        // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s
                        int numFields = 0;
 
                        // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s
                        int numFields = 0;
-                       boolean inNumeric = false;
+                       boolean isNumeric = false;
                        char currChar;
                        long[] fields = new long[4]; // needs to be long for lengthy decimals
                        long[] denoms = new long[4];
                        char currChar;
                        long[] fields = new long[4]; // needs to be long for lengthy decimals
                        long[] denoms = new long[4];
@@ -97,9 +97,9 @@ public abstract class Coordinate
                                        currChar = inString.charAt(i);
                                        if (currChar >= '0' && currChar <= '9')
                                        {
                                        currChar = inString.charAt(i);
                                        if (currChar >= '0' && currChar <= '9')
                                        {
-                                               if (!inNumeric)
+                                               if (!isNumeric)
                                                {
                                                {
-                                                       inNumeric = true;
+                                                       isNumeric = true;
                                                        numFields++;
                                                        denoms[numFields-1] = 1;
                                                }
                                                        numFields++;
                                                        denoms[numFields-1] = 1;
                                                }
@@ -111,7 +111,7 @@ public abstract class Coordinate
                                        }
                                        else
                                        {
                                        }
                                        else
                                        {
-                                               inNumeric = false;
+                                               isNumeric = false;
                                                // Remember delimiters
                                                if (currChar != ',' && currChar != '.') {otherDelims[numFields] = true;}
                                        }
                                                // Remember delimiters
                                                if (currChar != ',' && currChar != '.') {otherDelims[numFields] = true;}
                                        }
index 34d7bd72055275f81339e1fe23c434d2008603c6..4de97ef219bb60f05153dc4f3a4cbbdf72a2e672 100644 (file)
@@ -15,7 +15,8 @@ public class DataPoint
        private FieldList _fieldList = null;
        /** Special fields for coordinates */
        private Coordinate _latitude = null, _longitude = null;
        private FieldList _fieldList = null;
        /** Special fields for coordinates */
        private Coordinate _latitude = null, _longitude = null;
-       private Altitude _altitude;
+       private Altitude _altitude = null;
+       private Speed _hSpeed = null, _vSpeed = null;
        private Timestamp _timestamp = null;
        /** Attached photo */
        private Photo _photo = null;
        private Timestamp _timestamp = null;
        /** Attached photo */
        private Photo _photo = null;
@@ -26,13 +27,14 @@ public class DataPoint
        private boolean _markedForDeletion = false;
        private int _modifyCount = 0;
 
        private boolean _markedForDeletion = false;
        private int _modifyCount = 0;
 
+
        /**
         * Constructor
         * @param inValueArray array of String values
         * @param inFieldList list of fields
        /**
         * Constructor
         * @param inValueArray array of String values
         * @param inFieldList list of fields
-        * @param inAltFormat altitude format
+        * @param inOptions creation options such as units
         */
         */
-       public DataPoint(String[] inValueArray, FieldList inFieldList, Altitude.Format inAltFormat)
+       public DataPoint(String[] inValueArray, FieldList inFieldList, PointCreateOptions inOptions)
        {
                // save data
                _fieldValues = inValueArray;
        {
                // save data
                _fieldValues = inValueArray;
@@ -41,25 +43,42 @@ public class DataPoint
                // Remove double quotes around values
                removeQuotes(_fieldValues);
                // parse fields into objects
                // Remove double quotes around values
                removeQuotes(_fieldValues);
                // parse fields into objects
-               parseFields(null, inAltFormat);
+               parseFields(null, inOptions);
        }
 
 
        /**
         * Parse the string values into objects eg Coordinates
         * @param inField field which has changed, or null for all
        }
 
 
        /**
         * Parse the string values into objects eg Coordinates
         * @param inField field which has changed, or null for all
-        * @param inAltFormat altitude format
+        * @param inOptions creation options such as units
         */
         */
-       private void parseFields(Field inField, Altitude.Format inAltFormat)
+       private void parseFields(Field inField, PointCreateOptions inOptions)
        {
        {
+               if (inOptions == null) inOptions = new PointCreateOptions();
                if (inField == null || inField == Field.LATITUDE) {
                        _latitude = new Latitude(getFieldValue(Field.LATITUDE));
                }
                if (inField == null || inField == Field.LONGITUDE) {
                        _longitude = new Longitude(getFieldValue(Field.LONGITUDE));
                }
                if (inField == null || inField == Field.LATITUDE) {
                        _latitude = new Latitude(getFieldValue(Field.LATITUDE));
                }
                if (inField == null || inField == Field.LONGITUDE) {
                        _longitude = new Longitude(getFieldValue(Field.LONGITUDE));
                }
-               if (inField == null || inField == Field.ALTITUDE) {
-                       _altitude = new Altitude(getFieldValue(Field.ALTITUDE), inAltFormat);
+               if (inField == null || inField == Field.ALTITUDE)
+               {
+                       Unit altUnit = inOptions.getAltitudeUnits();
+                       if (_altitude != null && _altitude.getUnit() != null) {
+                               altUnit = _altitude.getUnit();
+                       }
+                       _altitude = new Altitude(getFieldValue(Field.ALTITUDE), altUnit);
+               }
+               if (inField == null || inField == Field.SPEED)
+               {
+                       _hSpeed = new Speed(getFieldValue(Field.SPEED), inOptions.getSpeedUnits());
+               }
+               if (inField == null || inField == Field.VERTICAL_SPEED)
+               {
+                       _vSpeed = new Speed(getFieldValue(Field.VERTICAL_SPEED), inOptions.getVerticalSpeedUnits());
+                       if (!inOptions.getVerticalSpeedsUpwards()) {
+                               _vSpeed.invert();
+                       }
                }
                if (inField == null || inField == Field.TIMESTAMP) {
                        _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
                }
                if (inField == null || inField == Field.TIMESTAMP) {
                        _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
@@ -160,13 +179,13 @@ public class DataPoint
                        setModified(inUndo);
                }
                // Change Coordinate, Altitude, Name or Timestamp fields after edit
                        setModified(inUndo);
                }
                // Change Coordinate, Altitude, Name or Timestamp fields after edit
-               if (_altitude != null && _altitude.getFormat() != Altitude.Format.NO_FORMAT) {
+               if (_altitude != null && _altitude.getUnit() != null) {
                        // Altitude already present so reuse format
                        // Altitude already present so reuse format
-                       parseFields(inField, _altitude.getFormat());
+                       parseFields(inField, null); // current units will be used
                }
                else {
                        // use default altitude format from config
                }
                else {
                        // use default altitude format from config
-                       parseFields(inField, Config.getUnitSet().getDefaultAltitudeFormat());
+                       parseFields(inField, Config.getUnitSet().getDefaultOptions());
                }
        }
 
                }
        }
 
@@ -219,13 +238,33 @@ public class DataPoint
        /** @return true if point has altitude */
        public boolean hasAltitude()
        {
        /** @return true if point has altitude */
        public boolean hasAltitude()
        {
-               return _altitude.isValid();
+               return _altitude != null && _altitude.isValid();
        }
        /** @return altitude */
        public Altitude getAltitude()
        {
                return _altitude;
        }
        }
        /** @return altitude */
        public Altitude getAltitude()
        {
                return _altitude;
        }
+       /** @return true if point has horizontal speed (loaded as field) */
+       public boolean hasHSpeed()
+       {
+               return _hSpeed != null && _hSpeed.isValid();
+       }
+       /** @return horizontal speed */
+       public Speed getHSpeed()
+       {
+               return _hSpeed;
+       }
+       /** @return true if point has vertical speed (loaded as field) */
+       public boolean hasVSpeed()
+       {
+               return _vSpeed != null && _vSpeed.isValid();
+       }
+       /** @return vertical speed */
+       public Speed getVSpeed()
+       {
+               return _vSpeed;
+       }
        /** @return true if point has timestamp */
        public boolean hasTimestamp()
        {
        /** @return true if point has timestamp */
        public boolean hasTimestamp()
        {
@@ -472,8 +511,20 @@ public class DataPoint
                // Copy all values (note that photo not copied)
                String[] valuesCopy = new String[_fieldValues.length];
                System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
                // Copy all values (note that photo not copied)
                String[] valuesCopy = new String[_fieldValues.length];
                System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
+
+               PointCreateOptions options = new PointCreateOptions();
+               if (_altitude != null) {
+                       options.setAltitudeUnits(_altitude.getUnit());
+               }
                // Make new object to hold cloned data
                // Make new object to hold cloned data
-               DataPoint point = new DataPoint(valuesCopy, _fieldList, _altitude.getFormat());
+               DataPoint point = new DataPoint(valuesCopy, _fieldList, options);
+               // Copy the speed information
+               if (hasHSpeed()) {
+                       point.getHSpeed().copyFrom(_hSpeed);
+               }
+               if (hasVSpeed()) {
+                       point.getVSpeed().copyFrom(_vSpeed);
+               }
                return point;
        }
 
                return point;
        }
 
index b6b8a8fb9cb9c7f1b647c9ef20d1d5779bbad16c..4e361224f420ed32b72473edc1e5adf87d37a3f0 100644 (file)
@@ -79,4 +79,22 @@ public class DoubleRange
        {
                return _max - _min;
        }
        {
                return _max - _min;
        }
+
+       /**
+        * @return mid value, halfway between min and max
+        */
+       public double getMidValue()
+       {
+               return (_max + _min) / 2.0;
+       }
+
+       /**
+        * Copy this range into a new object, which can then be modified without changing this one
+        * @return deep copy of this object
+        */
+       public DoubleRange copy()
+       {
+               if (_empty) return new DoubleRange();
+               return new DoubleRange(_min, _max);
+       }
 }
 }
index c0f4408f3637a23e932679bd441449928ea5777e..cc30325f8cc12751e2943dae6da6540bba4e953f 100644 (file)
@@ -219,6 +219,19 @@ public abstract class MediaList
                return false;
        }
 
                return false;
        }
 
+       /**
+        * @return true if there are any modified media in the list
+        */
+       public boolean hasModifiedMedia()
+       {
+               for (MediaObject m: _media) {
+                       if (m.isModified()) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
        /**
         * @return clone of list contents
         */
        /**
         * @return clone of list contents
         */
index 478618e4a5ac330795df9684600c4a54f3452e9c..8c53cafac220580338375300a6edbc583cacbd63 100644 (file)
@@ -211,6 +211,14 @@ public abstract class MediaObject
                return _currentStatus != Status.NOT_CONNECTED;
        }
 
                return _currentStatus != Status.NOT_CONNECTED;
        }
 
+       /**
+        * @return true if status has changed since load
+        */
+       public boolean isModified()
+       {
+               return _currentStatus != _originalStatus;
+       }
+
        /**
         * Reset any cached data (eg thumbnail)
         */
        /**
         * Reset any cached data (eg thumbnail)
         */
index 0c76981a12eab7b41da418514eace1e5e824c0e3..652e8243af2fc85d01dfb2940bc007bc0c755927 100644 (file)
@@ -9,11 +9,11 @@ import java.util.Locale;
  */
 public abstract class NumberUtils
 {
  */
 public abstract class NumberUtils
 {
-       /** Number formatter object to avoid lots of instantiations */
-       private static final NumberFormat NUM_FORMATTER = NumberFormat.getNumberInstance(Locale.UK);
+       /** UK-specific number formatter object to avoid lots of instantiations */
+       private static final NumberFormat UK_FORMAT = NumberFormat.getNumberInstance(Locale.UK);
        // Select the UK locale for this formatter so that decimal point is always used (not comma)
        static {
        // Select the UK locale for this formatter so that decimal point is always used (not comma)
        static {
-               if (NUM_FORMATTER instanceof DecimalFormat) ((DecimalFormat) NUM_FORMATTER).applyPattern("0.000");
+               if (UK_FORMAT instanceof DecimalFormat) ((DecimalFormat) UK_FORMAT).applyPattern("0.000");
        }
 
        /**
        }
 
        /**
@@ -42,14 +42,14 @@ public abstract class NumberUtils
        }
 
        /**
        }
 
        /**
-        * Format the given number to the given number of decimal places
+        * Format the given number in UK format (decimal point) to the given number of decimal places
         * @param inNumber double number to format
         * @param inDecimalPlaces number of decimal places
         */
         * @param inNumber double number to format
         * @param inDecimalPlaces number of decimal places
         */
-       public static String formatNumber(double inNumber, int inDecimalPlaces)
+       public static String formatNumberUk(double inNumber, int inDecimalPlaces)
        {
        {
-               NUM_FORMATTER.setMaximumFractionDigits(inDecimalPlaces);
-               NUM_FORMATTER.setMinimumFractionDigits(inDecimalPlaces);
-               return NUM_FORMATTER.format(inNumber);
+               UK_FORMAT.setMaximumFractionDigits(inDecimalPlaces);
+               UK_FORMAT.setMinimumFractionDigits(inDecimalPlaces);
+               return UK_FORMAT.format(inNumber);
        }
 }
\ No newline at end of file
        }
 }
\ No newline at end of file
diff --git a/tim/prune/data/PointCreateOptions.java b/tim/prune/data/PointCreateOptions.java
new file mode 100644 (file)
index 0000000..2f61336
--- /dev/null
@@ -0,0 +1,72 @@
+package tim.prune.data;
+
+/**
+ * Class to hold the options when creating (or loading) a new point,
+ * such as units for altitudes, speeds and vertical speeds
+ */
+public class PointCreateOptions
+{
+       private Unit _altitudeUnit  = UnitSetLibrary.UNITS_METRES;
+       private Unit _speedUnit     = UnitSetLibrary.SPEED_UNITS_METRESPERSEC;
+       private Unit _vertSpeedUnit = UnitSetLibrary.SPEED_UNITS_METRESPERSEC;
+       private boolean _vertSpeedsUpwards = true;
+
+       /**
+        * @param inUnit altitude units (only metres or feet accepted)
+        */
+       public void setAltitudeUnits(Unit inUnit)
+       {
+               if (inUnit == UnitSetLibrary.UNITS_METRES || inUnit == UnitSetLibrary.UNITS_FEET) {
+                       _altitudeUnit = inUnit;
+               }
+       }
+
+       /** @return altitude units */
+       public Unit getAltitudeUnits() {return _altitudeUnit;}
+
+       /**
+        * @param inUnit speed units (only m/s, ft/s, km/h and mph accepted)
+        */
+       public void setSpeedUnits(Unit inUnit)
+       {
+               if (inUnit == UnitSetLibrary.SPEED_UNITS_METRESPERSEC
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_FEETPERSEC
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_KMPERHOUR
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_MILESPERHOUR)
+               {
+                       _speedUnit = inUnit;
+               }
+       }
+
+       /** @return speed units */
+       public Unit getSpeedUnits() {return _speedUnit;}
+
+       /**
+        * @param inUnit speed units (only m/s, ft/s, km/h and mph accepted)
+        * @param inUpwards true if positive speeds are upwards, negative downwards
+        */
+       public void setVerticalSpeedUnits(Unit inUnit, boolean inUpwards)
+       {
+               if (inUnit == UnitSetLibrary.SPEED_UNITS_METRESPERSEC
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_FEETPERSEC
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_KMPERHOUR
+                       || inUnit == UnitSetLibrary.SPEED_UNITS_MILESPERHOUR)
+               {
+                       _vertSpeedUnit = inUnit;
+                       _vertSpeedsUpwards = inUpwards;
+               }
+       }
+
+       /** @return vertical speed units */
+       public Unit getVerticalSpeedUnits() {return _vertSpeedUnit;}
+
+       /** @return true if positive speeds are upwards, negative downwards */
+       public boolean getVerticalSpeedsUpwards() {return _vertSpeedsUpwards;}
+
+       /** for debug */
+       public String toString()
+       {
+               return "options: altitude " + _altitudeUnit.getNameKey() + ", speed " + _speedUnit.getNameKey() +
+                       ", vspeed " + _vertSpeedUnit.getNameKey() + (_vertSpeedsUpwards ? " (upwards)" : " (downwards)");
+       }
+}
index 180f3a5aef74e131d0c9bab9649310b4e840ef5e..a1338857e0c509acc6a3d6227f4044080e3f2914 100644 (file)
@@ -1,41 +1,19 @@
 package tim.prune.data;
 
 /**
 package tim.prune.data;
 
 /**
- * Class to manage the scaling of points
+ * Class to manage the scaling of points, used by the ThreeDModel
  */
 public class PointScaler
 {
        // Original data
        private Track _track = null;
  */
 public class PointScaler
 {
        // Original data
        private Track _track = null;
-       // Range information
-       private double _latMedian = 0.0;
-       private double _lonMedian = 0.0;
-       private int _minAltitude = 0;
-       // Scaling information
-       private double _longFactor = 0.0;
-       private double _altFactor = 0.0;
-       // Scaled points
+       // Scaled values
        private double[] _xValues = null;
        private double[] _yValues = null;
        private double[] _altValues = null;
        private double[] _xValues = null;
        private double[] _yValues = null;
        private double[] _altValues = null;
-       // max values
-       private double _maxX = 0.0;
-       private double _maxY = 0.0;
-       private double _maxScaledAlt = 0.0;
-       // lat/long lines
-       private double[] _latLinesDegs = null;
-       private double[] _lonLinesDegs = null;
-       private double[] _latLinesScaled = null;
-       private double[] _lonLinesScaled = null;
+       // Altitude range
+       private double _altitudeRange = 0.0;
 
 
-       // Constants
-       private static final double[] COORD_SEPARATIONS = {
-               1.0,                      // 1deg
-               30.0/60.0, 20.0/60.0,     // 30min, 20min
-               10.0/60.0, 5.0/60.0,      // 10min, 5min
-               3.0/60.0, 2.0/60.0, 1.0/60.0   // 3min, 2min, 1min
-       };
-       private static final int MAX_COORD_SEPARATION_INDEX = COORD_SEPARATIONS.length - 1;
 
        /**
         * Constructor
 
        /**
         * Constructor
@@ -52,78 +30,42 @@ public class PointScaler
         */
        public void scale()
        {
         */
        public void scale()
        {
-               // Clear data
-               DoubleRange latRange = new DoubleRange();
-               DoubleRange lonRange = new DoubleRange();
-               DoubleRange altRange = new DoubleRange();
-               int numPoints = 0;
-               int p = 0;
-               DataPoint point = null;
-               // Find limits of data
-               if (_track != null && (numPoints = _track.getNumPoints()) > 0)
+               // Work out extents
+               TrackExtents extents = new TrackExtents(_track);
+               extents.applySquareBorder();
+               final double horizDistance = 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();
+
+               // create new arrays for scaled values
+               if (_xValues == null || _xValues.length != numPoints)
                {
                {
-                       for (p=0; p<numPoints; p++)
-                       {
-                               point = _track.getPoint(p);
-                               if (point != null)
-                               {
-                                       latRange.addValue(point.getLatitude().getDouble());
-                                       lonRange.addValue(point.getLongitude().getDouble());
-                                       altRange.addValue(point.getAltitude().getValue(Altitude.Format.METRES));
-                               }
-                       }
+                       _xValues = new double[numPoints];
+                       _yValues = new double[numPoints];
+                       _altValues = new double[numPoints];
+               }
 
 
-                       // Find median latitude and calculate factor
-                       _latMedian = (latRange.getMinimum() + latRange.getMaximum()) / 2;
-                       _lonMedian = (lonRange.getMinimum() + lonRange.getMaximum()) / 2;
-                       _minAltitude = (int) altRange.getMinimum();
-                       _longFactor = Math.cos(_latMedian / 180.0 * Math.PI); // quite rough
-                       // Find altitude scale factor using distance
-                       DataPoint p1 = new DataPoint(new Latitude(latRange.getMinimum(), Coordinate.FORMAT_DEG),
-                               new Longitude(_lonMedian, Coordinate.FORMAT_DEG), null);
-                       DataPoint p2 = new DataPoint(new Latitude(latRange.getMaximum(), Coordinate.FORMAT_DEG),
-                               new Longitude(_lonMedian, Coordinate.FORMAT_DEG), null);
-                       double horizDist = Distance.convertRadiansToDistance(
-                               DataPoint.calculateRadiansBetween(p1, p2), UnitSetLibrary.UNITS_METRES); // both in m
-                       _altFactor = 1.0 / horizDist;
+               final double midXvalue = extents.getXRange().getMidValue();
+               final double midYvalue = extents.getYRange().getMidValue();
+               final double xyRange   = extents.getXRange().getRange();
 
 
-                       // create new arrays for scaled values
-                       if (_xValues == null || _xValues.length != numPoints)
-                       {
-                               _xValues = new double[numPoints];
-                               _yValues = new double[numPoints];
-                               _altValues = new double[numPoints];
-                       }
-                       // Calculate scaled values
-                       for (p=0; p<numPoints; p++)
+               // Calculate scaled values
+               for (int p=0; p<numPoints; p++)
+               {
+                       DataPoint point = _track.getPoint(p);
+                       if (point != null)
                        {
                        {
-                               point = _track.getPoint(p);
-                               if (point != null)
-                               {
-                                       _xValues[p] = getScaledLongitude(point.getLongitude().getDouble());
-                                       _yValues[p] = getScaledLatitude(point.getLatitude().getDouble());
-                                       _altValues[p] = getScaledAltitude(point.getAltitude());
-                                       if (_altValues[p] > _maxScaledAlt) {_maxScaledAlt = _altValues[p];}
-                               }
+                               _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;
                        }
                        }
-                       // Calculate x and y range
-                       _maxX = getScaledLongitude(lonRange.getMaximum());
-                       _maxY = getScaledLatitude(latRange.getMaximum());
                }
        }
 
 
                }
        }
 
 
-       /**
-        * @return maximum horiz value
-        */
-       public double getMaximumHoriz() { return _maxX; }
-       /**
-        * @return maximum vert value
-        */
-       public double getMaximumVert() { return _maxY; }
-
-       /** @return maximum scaled altitude value */
-       public double getMaxScaledAlt() { return _maxScaledAlt; }
 
        /**
         * Get the horizontal value for the specified point
 
        /**
         * Get the horizontal value for the specified point
@@ -134,6 +76,7 @@ public class PointScaler
        {
                return _xValues[inIndex];
        }
        {
                return _xValues[inIndex];
        }
+
        /**
         * Get the vertical value for the specified point
         * @param inIndex index of point, starting at 0
        /**
         * Get the vertical value for the specified point
         * @param inIndex index of point, starting at 0
@@ -143,6 +86,7 @@ public class PointScaler
        {
                return _yValues[inIndex];
        }
        {
                return _yValues[inIndex];
        }
+
        /**
         * Get the altitude value for the specified point
         * @param inIndex index of point, starting at 0
        /**
         * Get the altitude value for the specified point
         * @param inIndex index of point, starting at 0
@@ -154,165 +98,10 @@ public class PointScaler
        }
 
        /**
        }
 
        /**
-        * Scale the given latitude value
-        * @param inLatitude latitude in degrees
-        * @return scaled latitude
-        */
-       private double getScaledLatitude(double inLatitude)
-       {
-               return inLatitude - _latMedian;
-       }
-       /**
-        * Scale the given longitude value
-        * @param inLongitude longitude in degrees
-        * @return scaled longitude
-        */
-       private double getScaledLongitude(double inLongitude)
-       {
-               return (inLongitude - _lonMedian) * _longFactor;
-       }
-       /**
-        * Scale the given altitude value
-        * @param inAltitude Altitude object
-        * @return scaled altitude
-        */
-       private double getScaledAltitude(Altitude inAltitude)
-       {
-               if (inAltitude == null) return -1;
-               return (inAltitude.getValue(Altitude.Format.METRES) - _minAltitude) * _altFactor;
-       }
-
-       /**
-        * Unscale the given latitude value
-        * @param inScaledLatitude scaled latitude
-        * @return latitude in degrees
-        */
-       private double getUnscaledLatitude(double inScaledLatitude)
-       {
-               return inScaledLatitude + _latMedian;
-       }
-       /**
-        * Unscale the given longitude value
-        * @param inScaledLongitude scaled longitude
-        * @return longitude in degrees
-        */
-       private double getUnscaledLongitude(double inScaledLongitude)
-       {
-               return inScaledLongitude / _longFactor + _lonMedian;
-       }
-
-       /**
-        * Calculate the latitude and longitude lines
-        */
-       public void calculateLatLongLines()
-       {
-               double maxValue = getMaximumHoriz() > getMaximumVert() ?
-                       getMaximumHoriz():getMaximumVert();
-               // calculate boundaries in degrees
-               double minLong = getUnscaledLongitude(-maxValue);
-               double maxLong = getUnscaledLongitude(maxValue);
-               double minLat = getUnscaledLatitude(-maxValue);
-               double maxLat = getUnscaledLatitude(maxValue);
-               // work out what line separation to use to give at least two lines
-               int sepIndex = -1;
-               double separation;
-               int numLatLines = 0, numLonLines = 0;
-               do
-               {
-                       sepIndex++;
-                       separation = COORD_SEPARATIONS[sepIndex];
-                       numLatLines = getNumLinesBetween(minLat, maxLat, separation);
-                       numLonLines = getNumLinesBetween(minLong, maxLong, separation);
-               }
-               while ((numLonLines <= 1 || numLatLines <= 1) && sepIndex < MAX_COORD_SEPARATION_INDEX);
-               // create lines based on this separation
-               _latLinesDegs = getLines(minLat, maxLat, separation, numLatLines);
-               _lonLinesDegs = getLines(minLong, maxLong, separation, numLonLines);
-               // scale lines also
-               _latLinesScaled = new double[numLatLines];
-               for (int i=0; i<numLatLines; i++) _latLinesScaled[i] = getScaledLatitude(_latLinesDegs[i]);
-               _lonLinesScaled = new double[numLonLines];
-               for (int i=0; i<numLonLines; i++) _lonLinesScaled[i] = getScaledLongitude(_lonLinesDegs[i]);
-       }
-
-
-       /**
-        * Calculate the number of lines in the given range using the specified separation
-        * @param inMin minimum value
-        * @param inMax maximum value
-        * @param inSeparation line separation
-        * @return number of lines
-        */
-       private static int getNumLinesBetween(double inMin, double inMax, double inSeparation)
-       {
-               // Start looking from round number of degrees below minimum
-               double value = (int) inMin;
-               if (inMin < 0.0) value = value - 1.0;
-               // Loop until bigger than maximum
-               int numLines = 0;
-               while (value < inMax)
-               {
-                       if (value >= inMin) numLines++;
-                       value += inSeparation;
-               }
-               return numLines;
-       }
-
-
-       /**
-        * Get the line values in the given range using the specified separation
-        * @param inMin minimum value
-        * @param inMax maximum value
-        * @param inSeparation line separation
-        * @param inCount number of lines already counted
-        * @return array of line values
-        */
-       private static double[] getLines(double inMin, double inMax, double inSeparation, int inCount)
-       {
-               double[] values = new double[inCount];
-               // Start looking from round number of degrees below minimum
-               double value = (int) inMin;
-               if (inMin < 0.0) value = value - 1.0;
-               // Loop until bigger than maximum
-               int numLines = 0;
-               while (value < inMax)
-               {
-                       if (value >= inMin)
-                       {
-                               values[numLines] = value;
-                               numLines++;
-                       }
-                       value += inSeparation;
-               }
-               return values;
-       }
-
-       /**
-        * @return array of latitude lines in degrees
-        */
-       public double[] getLatitudeLines()
-       {
-               return _latLinesDegs;
-       }
-       /**
-        * @return array of longitude lines in degrees
-        */
-       public double[] getLongitudeLines()
-       {
-               return _lonLinesDegs;
-       }
-       /**
-        * @return array of latitude lines in scaled units
-        */
-       public double[] getScaledLatitudeLines()
-       {
-               return _latLinesScaled;
-       }
-       /**
-        * @return array of longitude lines in scaled units
+        * @return altitude range, in metres
         */
         */
-       public double[] getScaledLongitudeLines()
+       public double getAltitudeRange()
        {
        {
-               return _lonLinesScaled;
+               return _altitudeRange;
        }
 }
        }
 }
diff --git a/tim/prune/data/RangeStats.java b/tim/prune/data/RangeStats.java
new file mode 100644 (file)
index 0000000..0ed8fc7
--- /dev/null
@@ -0,0 +1,269 @@
+package tim.prune.data;
+
+import tim.prune.config.Config;
+
+/**
+ * Class to do calculations of range statistics such as distances, durations,
+ * speeds, gradients etc, and to hold the results of the calculations.
+ * Used by FullRangeDetails as well as the EstimateTime functions.
+ */
+public class RangeStats
+{
+       private boolean _valid = false;
+       private int     _numPoints   = 0;
+       private int     _startIndex = 0, _endIndex = 0;
+       private int     _numSegments = 0;
+       private AltitudeRange _totalAltitudeRange = null, _movingAltitudeRange = null;
+       private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null;
+       private Timestamp _earliestTimestamp = null, _latestTimestamp = null;
+       private long _movingMilliseconds = 0L;
+       private boolean _timestampsIncomplete = false;
+       private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
+       // Note, maximum speed is not calculated here, use the SpeedData method instead
+
+       private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
+
+
+       /**
+        * Constructor
+        * @param inTrack track to compile data for
+        * @param inStartIndex start index of range to examine
+        * @param inEndIndex end index (inclusive) of range to examine
+        */
+       public RangeStats(Track inTrack, int inStartIndex, int inEndIndex)
+       {
+               if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex
+                       && inEndIndex < inTrack.getNumPoints())
+               {
+                       _valid = calculateStats(inTrack, inStartIndex, inEndIndex);
+               }
+       }
+
+       /**
+        * Calculate the statistics and populate the member variables with the results
+        * @param inTrack track
+        * @param inStartIndex start index of range
+        * @param inEndIndex end index (inclusive) of range
+        * @return true on success
+        */
+       private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex)
+       {
+               _startIndex = inStartIndex;  _endIndex = inEndIndex;
+               _numPoints = inEndIndex - inStartIndex + 1;
+               _totalAltitudeRange  = new AltitudeRange();
+               _movingAltitudeRange = new AltitudeRange();
+               _gentleAltitudeRange = new AltitudeRange();
+               _steepAltitudeRange  = new AltitudeRange();
+               DataPoint prevPoint = null;
+               Altitude prevAltitude = null;
+               _totalDistanceRads = _movingDistanceRads = 0.0;
+               double radsSinceLastAltitude = 0.0;
+               _movingMilliseconds = 0L;
+
+               // Loop over the points in the range
+               for (int i=inStartIndex; i<= inEndIndex; i++)
+               {
+                       DataPoint p = inTrack.getPoint(i);
+                       if (p == null) return false;
+                       // ignore all waypoints
+                       if (p.isWaypoint()) continue;
+
+                       if (p.getSegmentStart()) {_numSegments++;}
+                       // Get the distance to the previous track point
+                       if (prevPoint != null)
+                       {
+                               double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
+                               _totalDistanceRads += rads;
+                               if (!p.getSegmentStart()) {
+                                       _movingDistanceRads += rads;
+                               }
+                               // Keep track of rads since last point with an altitude
+                               radsSinceLastAltitude += rads;
+                       }
+                       // Get the altitude difference to the previous track point
+                       if (p.hasAltitude())
+                       {
+                               Altitude altitude = p.getAltitude();
+                               _totalAltitudeRange.addValue(altitude);
+                               if (p.getSegmentStart()) {
+                                       _movingAltitudeRange.ignoreValue(altitude);
+                               }
+                               else
+                               {
+                                       _movingAltitudeRange.addValue(altitude);
+                                       if (prevAltitude != null)
+                                       {
+                                               // Work out gradient, see whether to ignore/add to gentle or steep
+                                               double heightDiff = altitude.getMetricValue() - prevAltitude.getMetricValue();
+                                               double metricDist = Distance.convertRadiansToDistance(radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
+                                               final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
+                                               if (isSteep) {
+                                                       _steepAltitudeRange.ignoreValue(prevAltitude);
+                                                       _steepAltitudeRange.addValue(altitude);
+                                               }
+                                               else {
+                                                       _gentleAltitudeRange.ignoreValue(prevAltitude);
+                                                       _gentleAltitudeRange.addValue(altitude);
+                                               }
+                                       }
+                               }
+                               prevAltitude = altitude;
+                               radsSinceLastAltitude = 0.0;
+                       }
+
+                       if (p.hasTimestamp())
+                       {
+                               if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
+                                       _earliestTimestamp = p.getTimestamp();
+                               }
+                               if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
+                                       _latestTimestamp = p.getTimestamp();
+                               }
+                               // Work out duration without segment gaps
+                               if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
+                               {
+                                       long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
+                                       if (millisLater < 0) {_timestampsIncomplete = true;}
+                                       _movingMilliseconds += millisLater;
+                               }
+                       }
+                       else if (!p.getSegmentStart()) {
+                               _timestampsIncomplete = true;
+                       }
+
+                       prevPoint = p;
+               }
+               return true;
+       }
+
+
+       /** @return true if results are valid */
+       public boolean isValid() {
+               return _valid;
+       }
+
+       /** @return start index of range */
+       public int getStartIndex() {
+               return _startIndex;
+       }
+
+       /** @return end index of range */
+       public int getEndIndex() {
+               return _endIndex;
+       }
+
+       /** @return number of points in range */
+       public int getNumPoints() {
+               return _numPoints;
+       }
+
+       /** @return number of segments in range */
+       public int getNumSegments() {
+               return _numSegments;
+       }
+
+       /** @return altitude range of range including segment gaps */
+       public AltitudeRange getTotalAltitudeRange() {
+               return _totalAltitudeRange;
+       }
+
+       /** @return altitude range of range just within segments */
+       public AltitudeRange getMovingAltitudeRange() {
+               return _movingAltitudeRange;
+       }
+
+       /** @return altitude range of range just considering low gradient bits */
+       public AltitudeRange getGentleAltitudeRange() {
+               return _gentleAltitudeRange;
+       }
+
+       /** @return altitude range of range just considering high gradient bits */
+       public AltitudeRange getSteepAltitudeRange() {
+               return _steepAltitudeRange;
+       }
+
+       /** @return the earliest timestamp found */
+       public Timestamp getEarliestTimestamp() {
+               return _earliestTimestamp;
+       }
+
+       /** @return the latest timestamp found */
+       public Timestamp getLatestTimestamp() {
+               return _latestTimestamp;
+       }
+
+       /** @return total number of seconds in the range */
+       public long getTotalDurationInSeconds()
+       {
+               if (_earliestTimestamp != null && _latestTimestamp != null) {
+                       return _latestTimestamp.getSecondsSince(_earliestTimestamp);
+               }
+               return 0L;
+       }
+
+       /** @return number of seconds within the segments of the range */
+       public long getMovingDurationInSeconds()
+       {
+               return _movingMilliseconds / 1000;
+       }
+
+       /** @return true if any timestamps are missing or out of sequence */
+       public boolean getTimestampsIncomplete() {
+               return _timestampsIncomplete;
+       }
+
+       /** @return total distance in the current distance units (km or mi) */
+       public double getTotalDistance() {
+               return Distance.convertRadiansToDistance(_totalDistanceRads);
+       }
+
+       /** @return moving distance in the current distance units (km or mi) */
+       public double getMovingDistance() {
+               return Distance.convertRadiansToDistance(_movingDistanceRads);
+       }
+
+       /** @return moving distance in km */
+       public double getMovingDistanceKilometres() {
+               return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
+       }
+
+       /** @return the total gradient in % (including segment gaps) */
+       public double getTotalGradient()
+       {
+               double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
+               if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
+                       return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
+               }
+               return 0.0;
+       }
+
+       /** @return the moving gradient in % (ignoring segment gaps) */
+       public double getMovingGradient()
+       {
+               double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
+               if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
+                       return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
+               }
+               return 0.0;
+       }
+
+       /** @return the total vertical speed (including segment gaps) in current vspeed units */
+       public double getTotalVerticalSpeed()
+       {
+               long time = getTotalDurationInSeconds();
+               if (time > 0 && _totalAltitudeRange.hasRange()) {
+                       return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
+               }
+               return 0.0;
+       }
+
+       /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
+       public double getMovingVerticalSpeed()
+       {
+               long time = getMovingDurationInSeconds();
+               if (time > 0 && _movingAltitudeRange.hasRange()) {
+                       return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
+               }
+               return 0.0;
+       }
+}
index f5c41ffd91f6015d8d4762e546fe8385c5dadc8e..6125eacad76e7b0390daef85fa11e14bd65573fb 100644 (file)
@@ -19,7 +19,6 @@ public class Selection
        private AltitudeRange _altitudeRange = null;
        private long _totalSeconds = 0L, _movingSeconds = 0L;
        private double _angDistance = -1.0, _angMovingDistance = -1.0;
        private AltitudeRange _altitudeRange = null;
        private long _totalSeconds = 0L, _movingSeconds = 0L;
        private double _angDistance = -1.0, _angMovingDistance = -1.0;
-       private int _numSegments = 0;
 
 
        /**
 
 
        /**
@@ -64,7 +63,6 @@ public class Selection
         */
        private void recalculate()
        {
         */
        private void recalculate()
        {
-               _numSegments = 0;
                final int numPoints = _track.getNumPoints();
                // Recheck if the number of points has changed
                if (numPoints != _prevNumPoints) {
                final int numPoints = _track.getNumPoints();
                // Recheck if the number of points has changed
                if (numPoints != _prevNumPoints) {
@@ -109,16 +107,11 @@ public class Selection
                                        {
                                                double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
                                                _angDistance += radians;
                                        {
                                                double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
                                                _angDistance += radians;
-                                               if (currPoint.getSegmentStart()) {
-                                                       _numSegments++;
-                                               }
-                                               else {
+                                               if (!currPoint.getSegmentStart()) {
                                                        _angMovingDistance += radians;
                                                }
                                        }
                                        lastPoint = currPoint;
                                                        _angMovingDistance += radians;
                                                }
                                        }
                                        lastPoint = currPoint;
-                                       // If it's a track point then there must be at least one segment
-                                       if (_numSegments == 0) {_numSegments = 1;}
                                }
                        }
                        if (endTime != null) {
                                }
                        }
                        if (endTime != null) {
@@ -192,14 +185,6 @@ public class Selection
                return Distance.convertRadiansToDistance(_angMovingDistance);
        }
 
                return Distance.convertRadiansToDistance(_angMovingDistance);
        }
 
-       /**
-        * @return number of segments in selection
-        */
-       public int getNumSegments()
-       {
-               return _numSegments;
-       }
-
        /**
         * Clear selected point, range, photo and audio
         */
        /**
         * Clear selected point, range, photo and audio
         */
diff --git a/tim/prune/data/Speed.java b/tim/prune/data/Speed.java
new file mode 100644 (file)
index 0000000..80cb7df
--- /dev/null
@@ -0,0 +1,102 @@
+package tim.prune.data;
+
+/**
+ * Class to hold either a horizontal speed or a vertical speed
+ * including the units
+ */
+public class Speed
+{
+       private double _value = 0.0;
+       private Unit   _unit  = null;
+       private boolean _valid = false;
+
+       /**
+        * Constructor
+        * @param inValue value
+        * @param inUnit unit, such as m/s or km/h
+        */
+       public Speed(double inValue, Unit inUnit)
+       {
+               _value = inValue;
+               _unit  = inUnit;
+               _valid = isValidUnit(inUnit);
+       }
+
+       /**
+        * Constructor
+        * @param inValue value as string
+        * @param inUnit unit, such as m/s or km/h
+        */
+       public Speed(String inValue, Unit inUnit)
+       {
+               try {
+                       _value = Double.parseDouble(inValue);
+                       _unit  = inUnit;
+                       _valid = isValidUnit(inUnit);
+               }
+               catch (Exception e)
+               {
+                       _valid = false;
+               }
+       }
+
+       /**
+        * Check if the given unit is valid for a speed
+        * @param inUnit unit
+        * @return true if it's valid
+        */
+       private static boolean isValidUnit(Unit inUnit)
+       {
+               return inUnit != null && (inUnit == UnitSetLibrary.SPEED_UNITS_METRESPERSEC
+                               || inUnit == UnitSetLibrary.SPEED_UNITS_KMPERHOUR
+                               || inUnit == UnitSetLibrary.SPEED_UNITS_FEETPERSEC
+                               || inUnit == UnitSetLibrary.SPEED_UNITS_MILESPERHOUR
+                               || inUnit == UnitSetLibrary.SPEED_UNITS_KNOTS);
+       }
+
+       /**
+        * Invert the speed value, for example when vertical speeds are positive downwards
+        */
+       public void invert() {
+               if (_valid) _value = -_value;
+       }
+
+       /** @return the numerical value in whatever units they're in */
+       public double getValue() {return _value;}
+
+       /** @return the units they're in */
+       public Unit getUnit() {return _unit;}
+
+       /**
+        * @return speed value in metres per second
+        */
+       public double getValueInMetresPerSec()
+       {
+               if (!_valid) return 0.0;
+               return _value / _unit.getMultFactorFromStd();
+       }
+
+       /**
+        * @param inUnit specified speed units
+        * @return speed value in the specified units
+        */
+       public double getValue(Unit inUnit)
+       {
+               if (!_valid || !isValidUnit(inUnit)) return 0.0;
+               return getValueInMetresPerSec() * inUnit.getMultFactorFromStd();
+       }
+
+       /** @return true if this is valid */
+       public boolean isValid() {return _valid;}
+
+       /**
+        * Copy the values from the other object into this one, to make this one a clone
+        * @param inOther other speed object
+        */
+       public void copyFrom(Speed inOther)
+       {
+               _value = inOther._value;
+               _unit  = inOther._unit;
+               _valid = inOther._valid;
+       }
+}
index d83a7bd306ff9f72aa480f2ce0619ce0f5239dbb..f6f429c7d477bc13cb29a5a9bb3212e467e99fcf 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.config.Config;
 public abstract class SpeedCalculator
 {
        /**
 public abstract class SpeedCalculator
 {
        /**
-        * Calculate the speed value of the track at the specified index
+        * Calculate the horizontal speed value of the track at the specified index
         * @param inTrack track object
         * @param inIndex index of point to calculate speed for
         * @param inValue object in which to place result of calculation
         * @param inTrack track object
         * @param inIndex index of point to calculate speed for
         * @param inValue object in which to place result of calculation
@@ -28,13 +28,10 @@ public abstract class SpeedCalculator
                double  speedValue = 0.0;
 
                // First, see if point has a speed value already
                double  speedValue = 0.0;
 
                // First, see if point has a speed value already
-               // FIXME: How do we know what units this speed is in?  m/s or mph or km/h or what?
-               String speedStr = point.getFieldValue(Field.SPEED);
-               try {
-                       speedValue = Double.parseDouble(speedStr);
+               if (point.hasHSpeed()) {
+                       speedValue = point.getHSpeed().getValue(Config.getUnitSet().getSpeedUnit());
                        pointHasSpeed = true;
                }
                        pointHasSpeed = true;
                }
-               catch (Exception e) {} // ignore, leave pointHasSpeed false
 
                // otherwise, see if we can calculate it from the timestamps
                if (!pointHasSpeed && point.hasTimestamp() && !point.isWaypoint())
 
                // otherwise, see if we can calculate it from the timestamps
                if (!pointHasSpeed && point.hasTimestamp() && !point.isWaypoint())
@@ -127,15 +124,10 @@ public abstract class SpeedCalculator
                double  speedValue = 0.0;
 
                // First, see if point has a speed value already
                double  speedValue = 0.0;
 
                // First, see if point has a speed value already
-               if (point != null)
+               if (point != null && point.hasVSpeed())
                {
                {
-                       // FIXME: Can we assume m/s or ft/s?
-                       String speedStr = point.getFieldValue(Field.VERTICAL_SPEED);
-                       try {
-                               speedValue = Double.parseDouble(speedStr);
-                               pointHasSpeed = true;
-                       }
-                       catch (Exception e) {} // ignore, leave pointHasSpeed false
+                       speedValue = point.getVSpeed().getValue(Config.getUnitSet().getVerticalSpeedUnit());
+                       pointHasSpeed = true;
                }
                // otherwise, see if we can calculate it from the heights and timestamps
                if (!pointHasSpeed
                }
                // otherwise, see if we can calculate it from the heights and timestamps
                if (!pointHasSpeed
index 477a7227125a0a82a7a4fcd4a7c899d2352bc03a..623d80497eb995e36073e31e6067926192e9c626 100644 (file)
@@ -1,10 +1,11 @@
 package tim.prune.data;
 
 import java.text.DateFormat;
 package tim.prune.data;
 
 import java.text.DateFormat;
-import java.text.ParseException;
+import java.text.ParsePosition;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -26,7 +27,8 @@ public class Timestamp
        private static DateFormat[] ALL_DATE_FORMATS = null;
        private static Calendar CALENDAR = null;
        private static final Pattern ISO8601_FRACTIONAL_PATTERN
        private static DateFormat[] ALL_DATE_FORMATS = null;
        private static Calendar CALENDAR = null;
        private static final Pattern ISO8601_FRACTIONAL_PATTERN
-               = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{1,3})Z?");
+               = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:[\\.,](\\d{1,3}))?(Z|[\\+-]\\d{2}(?::?\\d{2})?)?");
+           //                    year     month     day T  hour    minute    sec             millisec   Z or +/-  hours  :   minutes
        private static final Pattern GENERAL_TIMESTAMP_PATTERN
                = Pattern.compile("(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})");
        private static long SECS_SINCE_1970 = 0L;
        private static final Pattern GENERAL_TIMESTAMP_PATTERN
                = Pattern.compile("(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})");
        private static long SECS_SINCE_1970 = 0L;
@@ -68,12 +70,17 @@ public class Timestamp
        static
        {
                CALENDAR = Calendar.getInstance();
        static
        {
                CALENDAR = Calendar.getInstance();
+               TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+               CALENDAR.setTimeZone(gmtZone);
                MSECS_SINCE_1970 = CALENDAR.getTimeInMillis();
                SECS_SINCE_1970 = MSECS_SINCE_1970 / 1000L;
                SECS_SINCE_GARTRIP = SECS_SINCE_1970 - GARTRIP_OFFSET;
                CALENDAR.add(Calendar.YEAR, -20);
                MSECS_SINCE_1990 = CALENDAR.getTimeInMillis();
                TWENTY_YEARS_IN_SECS = (MSECS_SINCE_1970 - MSECS_SINCE_1990) / 1000L;
                MSECS_SINCE_1970 = CALENDAR.getTimeInMillis();
                SECS_SINCE_1970 = MSECS_SINCE_1970 / 1000L;
                SECS_SINCE_GARTRIP = SECS_SINCE_1970 - GARTRIP_OFFSET;
                CALENDAR.add(Calendar.YEAR, -20);
                MSECS_SINCE_1990 = CALENDAR.getTimeInMillis();
                TWENTY_YEARS_IN_SECS = (MSECS_SINCE_1970 - MSECS_SINCE_1990) / 1000L;
+               // Set timezone for output
+               ISO_8601_FORMAT.setTimeZone(gmtZone);
+               DEFAULT_DATE_FORMAT.setTimeZone(gmtZone);
                // Date formats
                ALL_DATE_FORMATS = new DateFormat[] {
                        DEFAULT_DATE_FORMAT,
                // Date formats
                ALL_DATE_FORMATS = new DateFormat[] {
                        DEFAULT_DATE_FORMAT,
@@ -145,7 +152,8 @@ public class Timestamp
                                                        Integer.parseInt(fmatcher.group(4)), // hour
                                                        Integer.parseInt(fmatcher.group(5)), // minute
                                                        Integer.parseInt(fmatcher.group(6)), // second
                                                        Integer.parseInt(fmatcher.group(4)), // hour
                                                        Integer.parseInt(fmatcher.group(5)), // minute
                                                        Integer.parseInt(fmatcher.group(6)), // second
-                                                       fmatcher.group(7));                  // fractional seconds
+                                                       fmatcher.group(7),                   // fractional seconds
+                                                       fmatcher.group(8));                  // timezone, if any
                                                return true;
                                        }
                                        catch (NumberFormatException nfe) {}
                                                return true;
                                        }
                                        catch (NumberFormatException nfe) {}
@@ -173,7 +181,7 @@ public class Timestamp
                                                                Integer.parseInt(matcher.group(4)),
                                                                Integer.parseInt(matcher.group(5)),
                                                                Integer.parseInt(matcher.group(6)),
                                                                Integer.parseInt(matcher.group(4)),
                                                                Integer.parseInt(matcher.group(5)),
                                                                Integer.parseInt(matcher.group(6)),
-                                                               null); // no fractions of a second
+                                                               null, null); // no fractions of a second and no timezone
                                                        return true;
                                                }
                                                catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
                                                        return true;
                                                }
                                                catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
@@ -193,14 +201,16 @@ public class Timestamp
         */
        private boolean parseString(String inString, DateFormat inDateFormat)
        {
         */
        private boolean parseString(String inString, DateFormat inDateFormat)
        {
-               try
+               inDateFormat.setLenient(false);
+               ParsePosition pPos = new ParsePosition(0);
+               Date date = inDateFormat.parse(inString, pPos);
+               if (date != null && inString.length() == pPos.getIndex()) // require use of _all_ the string, not just the beginning
                {
                {
-                       Date date = inDateFormat.parse(inString);
                        CALENDAR.setTime(date);
                        _milliseconds = CALENDAR.getTimeInMillis();
                        return true;
                }
                        CALENDAR.setTime(date);
                        _milliseconds = CALENDAR.getTimeInMillis();
                        return true;
                }
-               catch (ParseException e) {}
+
                return false;
        }
 
                return false;
        }
 
@@ -216,7 +226,7 @@ public class Timestamp
         */
        public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
        {
         */
        public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
        {
-               _milliseconds = getMilliseconds(inYear, inMonth, inDay, inHour, inMinute, inSecond, null);
+               _milliseconds = getMilliseconds(inYear, inMonth, inDay, inHour, inMinute, inSecond, null, null);
                _valid = true;
        }
 
                _valid = true;
        }
 
@@ -241,12 +251,22 @@ public class Timestamp
         * @param inMinute minute
         * @param inSecond seconds
         * @param inFraction fractions of a second
         * @param inMinute minute
         * @param inSecond seconds
         * @param inFraction fractions of a second
+        * @param inTimezone timezone, if any
         * @return number of milliseconds
         */
        private static long getMilliseconds(int inYear, int inMonth, int inDay,
         * @return number of milliseconds
         */
        private static long getMilliseconds(int inYear, int inMonth, int inDay,
-               int inHour, int inMinute, int inSecond, String inFraction)
+               int inHour, int inMinute, int inSecond, String inFraction, String inTimezone)
        {
                Calendar cal = Calendar.getInstance();
        {
                Calendar cal = Calendar.getInstance();
+               // Timezone, if any
+               if (inTimezone == null || inTimezone.equals("") || inTimezone.equals("Z")) {
+                       // No timezone, use zulu
+                       cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+               }
+               else {
+                       // Timezone specified, pass to calendar
+                       cal.setTimeZone(TimeZone.getTimeZone("GMT" + inTimezone));
+               }
                cal.set(Calendar.YEAR, inYear);
                cal.set(Calendar.MONTH, inMonth - 1);
                cal.set(Calendar.DAY_OF_MONTH, inDay);
                cal.set(Calendar.YEAR, inYear);
                cal.set(Calendar.MONTH, inMonth - 1);
                cal.set(Calendar.DAY_OF_MONTH, inDay);
@@ -426,7 +446,7 @@ public class Timestamp
                        return format(ISO_8601_FORMAT);
                }
                if (_text == null) {
                        return format(ISO_8601_FORMAT);
                }
                if (_text == null) {
-                       _text = (_valid?format(DEFAULT_DATE_FORMAT):"");
+                       _text = format(DEFAULT_DATE_FORMAT);
                }
                return _text;
        }
                }
                return _text;
        }
@@ -453,6 +473,7 @@ public class Timestamp
         */
        private String format(DateFormat inFormat)
        {
         */
        private String format(DateFormat inFormat)
        {
+               CALENDAR.setTimeZone(TimeZone.getTimeZone("GMT"));
                CALENDAR.setTimeInMillis(_milliseconds);
                return inFormat.format(CALENDAR.getTime());
        }
                CALENDAR.setTimeInMillis(_milliseconds);
                return inFormat.format(CALENDAR.getTime());
        }
@@ -463,6 +484,7 @@ public class Timestamp
        public Calendar getCalendar()
        {
                Calendar cal = Calendar.getInstance();
        public Calendar getCalendar()
        {
                Calendar cal = Calendar.getInstance();
+               cal.setTimeZone(TimeZone.getTimeZone("GMT"));
                cal.setTimeInMillis(_milliseconds);
                return cal;
        }
                cal.setTimeInMillis(_milliseconds);
                return cal;
        }
index b28fd3642091cdc485e1a9130a72c3f2ecd14dac..8623c587ba5c9131d9051ad083cdcafffd24e54c 100644 (file)
@@ -62,9 +62,9 @@ public class Track
         * Load method, for initialising and reinitialising data
         * @param inFieldArray array of Field objects describing fields
         * @param inPointArray 2d object array containing data
         * Load method, for initialising and reinitialising data
         * @param inFieldArray array of Field objects describing fields
         * @param inPointArray 2d object array containing data
-        * @param inAltFormat altitude format
+        * @param inOptions load options such as units
         */
         */
-       public void load(Field[] inFieldArray, Object[][] inPointArray, Altitude.Format inAltFormat)
+       public void load(Field[] inFieldArray, Object[][] inPointArray, PointCreateOptions inOptions)
        {
                if (inFieldArray == null || inPointArray == null)
                {
        {
                if (inFieldArray == null || inPointArray == null)
                {
@@ -81,7 +81,7 @@ public class Track
                {
                        dataArray = (String[]) inPointArray[p];
                        // Convert to DataPoint objects
                {
                        dataArray = (String[]) inPointArray[p];
                        // Convert to DataPoint objects
-                       DataPoint point = new DataPoint(dataArray, _masterFieldList, inAltFormat);
+                       DataPoint point = new DataPoint(dataArray, _masterFieldList, inOptions);
                        if (point.isValid())
                        {
                                _dataPoints[pointIndex] = point;
                        if (point.isValid())
                        {
                                _dataPoints[pointIndex] = point;
@@ -333,12 +333,12 @@ public class Track
         * @param inStart start of range
         * @param inEnd end of range
         * @param inOffset offset to add (-ve to subtract)
         * @param inStart start of range
         * @param inEnd end of range
         * @param inOffset offset to add (-ve to subtract)
-        * @param inFormat altitude format of offset
+        * @param inUnit altitude unit of offset
         * @param inDecimals number of decimal places in offset
         * @return true on success
         */
        public boolean addAltitudeOffset(int inStart, int inEnd, double inOffset,
         * @param inDecimals number of decimal places in offset
         * @return true on success
         */
        public boolean addAltitudeOffset(int inStart, int inEnd, double inOffset,
-        Altitude.Format inFormat, int inDecimals)
+        Unit inUnit, int inDecimals)
        {
                // sanity check
                if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
        {
                // sanity check
                if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
@@ -353,7 +353,7 @@ public class Track
                        {
                                // This point has an altitude so add the offset to it
                                foundAlt = true;
                        {
                                // This point has an altitude so add the offset to it
                                foundAlt = true;
-                               alt.addOffset(inOffset, inFormat, inDecimals);
+                               alt.addOffset(inOffset, inUnit, inDecimals);
                                _dataPoints[i].setModified(false);
                        }
                }
                                _dataPoints[i].setModified(false);
                        }
                }
@@ -578,18 +578,19 @@ public class Track
                double latitudeDiff = 0.0, longitudeDiff = 0.0;
                double totalAltitude = 0;
                int numAltitudes = 0;
                double latitudeDiff = 0.0, longitudeDiff = 0.0;
                double totalAltitude = 0;
                int numAltitudes = 0;
-               Altitude.Format altFormat = Altitude.Format.NO_FORMAT;
+               Unit altUnit = null;
                // loop between start and end points
                for (int i=inStartIndex; i<= inEndIndex; i++)
                {
                        DataPoint currPoint = getPoint(i);
                        latitudeDiff += (currPoint.getLatitude().getDouble() - firstLatitude);
                        longitudeDiff += (currPoint.getLongitude().getDouble() - firstLongitude);
                // loop between start and end points
                for (int i=inStartIndex; i<= inEndIndex; i++)
                {
                        DataPoint currPoint = getPoint(i);
                        latitudeDiff += (currPoint.getLatitude().getDouble() - firstLatitude);
                        longitudeDiff += (currPoint.getLongitude().getDouble() - firstLongitude);
-                       if (currPoint.hasAltitude()) {
-                               totalAltitude += currPoint.getAltitude().getValue(altFormat);
+                       if (currPoint.hasAltitude())
+                       {
+                               totalAltitude += currPoint.getAltitude().getValue(altUnit);
                                // Use altitude format of first valid altitude
                                // Use altitude format of first valid altitude
-                               if (altFormat == Altitude.Format.NO_FORMAT)
-                                       altFormat = currPoint.getAltitude().getFormat();
+                               if (altUnit == null)
+                                       altUnit = currPoint.getAltitude().getUnit();
                                numAltitudes++;
                        }
                }
                                numAltitudes++;
                        }
                }
@@ -597,7 +598,9 @@ public class Track
                double meanLatitude = firstLatitude + (latitudeDiff / numPoints);
                double meanLongitude = firstLongitude + (longitudeDiff / numPoints);
                Altitude meanAltitude = null;
                double meanLatitude = firstLatitude + (latitudeDiff / numPoints);
                double meanLongitude = firstLongitude + (longitudeDiff / numPoints);
                Altitude meanAltitude = null;
-               if (numAltitudes > 0) {meanAltitude = new Altitude((int) (totalAltitude / numAltitudes), altFormat);}
+               if (numAltitudes > 0) {
+                       meanAltitude = new Altitude((int) (totalAltitude / numAltitudes), altUnit);
+               }
 
                DataPoint insertedPoint = new DataPoint(new Latitude(meanLatitude, Coordinate.FORMAT_NONE),
                        new Longitude(meanLongitude, Coordinate.FORMAT_NONE), meanAltitude);
 
                DataPoint insertedPoint = new DataPoint(new Latitude(meanLatitude, Coordinate.FORMAT_NONE),
                        new Longitude(meanLongitude, Coordinate.FORMAT_NONE), meanAltitude);
@@ -657,7 +660,7 @@ public class Track
         */
        public DoubleRange getXRange()
        {
         */
        public DoubleRange getXRange()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _xRange;
        }
 
                return _xRange;
        }
 
@@ -666,7 +669,7 @@ public class Track
         */
        public DoubleRange getYRange()
        {
         */
        public DoubleRange getYRange()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _yRange;
        }
 
                return _yRange;
        }
 
@@ -675,7 +678,7 @@ public class Track
         */
        public DoubleRange getLatRange()
        {
         */
        public DoubleRange getLatRange()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _latRange;
        }
        /**
                return _latRange;
        }
        /**
@@ -683,7 +686,7 @@ public class Track
         */
        public DoubleRange getLonRange()
        {
         */
        public DoubleRange getLonRange()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _longRange;
        }
 
                return _longRange;
        }
 
@@ -693,7 +696,7 @@ public class Track
         */
        public double getX(int inPointNum)
        {
         */
        public double getX(int inPointNum)
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _xValues[inPointNum];
        }
 
                return _xValues[inPointNum];
        }
 
@@ -703,7 +706,7 @@ public class Track
         */
        public double getY(int inPointNum)
        {
         */
        public double getY(int inPointNum)
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _yValues[inPointNum];
        }
 
                return _yValues[inPointNum];
        }
 
@@ -770,7 +773,7 @@ public class Track
         */
        public boolean hasTrackPoints()
        {
         */
        public boolean hasTrackPoints()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _hasTrackpoint;
        }
 
                return _hasTrackpoint;
        }
 
@@ -779,7 +782,7 @@ public class Track
         */
        public boolean hasWaypoints()
        {
         */
        public boolean hasWaypoints()
        {
-               if (!_scaled) scalePoints();
+               if (!_scaled) {scalePoints();}
                return _hasWaypoint;
        }
 
                return _hasWaypoint;
        }
 
@@ -862,7 +865,7 @@ public class Track
         * Scale all the points in the track to gain x and y values
         * ready for plotting
         */
         * Scale all the points in the track to gain x and y values
         * ready for plotting
         */
-       private void scalePoints()
+       private synchronized void scalePoints()
        {
                // Loop through all points in track, to see limits of lat, long
                _longRange = new DoubleRange();
        {
                // Loop through all points in track, to see limits of lat, long
                _longRange = new DoubleRange();
diff --git a/tim/prune/data/TrackExtents.java b/tim/prune/data/TrackExtents.java
new file mode 100644 (file)
index 0000000..0bf3081
--- /dev/null
@@ -0,0 +1,103 @@
+package tim.prune.data;
+
+/**
+ * Class to hold the extents of a track, in 2d and in 3d,
+ * and to calculate a square area with a default border
+ */
+public class TrackExtents
+{
+       /** Track object */
+       private Track _track = null;
+       /** X and Y ranges */
+       private DoubleRange _xRange   = null, _yRange   = null;
+
+       /** Border multiplier */
+       private static final double BORDER_MULTIPLIER = 1.1; // 10% border
+
+       /**
+        * Constructor
+        * @param inTrack track object to take extents from
+        */
+       public TrackExtents(Track inTrack)
+       {
+               _track  = inTrack;
+               _xRange = inTrack.getXRange().copy();
+               _yRange = inTrack.getYRange().copy();
+       }
+
+
+       /**
+        * Make the x and y ranges square with a default border around
+        */
+       public void applySquareBorder()
+       {
+               // Find the middle of the x and y
+               final double midXvalue = _xRange.getMidValue();
+               final double midYvalue = _yRange.getMidValue();
+               // Find x and y range, take maximum
+               double xyRange = Math.max(_xRange.getRange(), _yRange.getRange()) * BORDER_MULTIPLIER;
+               if (getHorizontalDistanceMetres() < 10.0)
+               {
+                       // all the points are near enough on the same spot, expand scale to avoid dividing by zero
+                       xyRange = 0.1;
+               }
+
+               // Apply these new min and max to the ranges
+               _xRange.addValue(midXvalue - xyRange / 2.0);
+               _xRange.addValue(midXvalue + xyRange / 2.0);
+               _yRange.addValue(midYvalue - xyRange / 2.0);
+               _yRange.addValue(midYvalue + xyRange / 2.0);
+       }
+
+       /** @return x range */
+       public DoubleRange getXRange() {
+               return _xRange;
+       }
+
+       /** @return y range */
+       public DoubleRange getYRange() {
+               return _yRange;
+       }
+
+       /** @return altitude range */
+       public DoubleRange getAltitudeRange()
+       {
+               final int numPoints = _track.getNumPoints();
+               DoubleRange altRange = new DoubleRange();
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint p = _track.getPoint(i);
+                       if (p != null && p.hasAltitude()) {
+                               altRange.addValue(p.getAltitude().getMetricValue());
+                       }
+               }
+               return altRange;
+       }
+
+       /**
+        * @return the greater of the N/S and E/W extent of the track, in metres (including border)
+        */
+       public double getHorizontalDistanceMetres()
+       {
+               DoubleRange lonRange = _track.getLonRange();
+               DoubleRange latRange = _track.getLatRange();
+
+               // Find horizontal and vertical extents of enclosing rectangle
+               DataPoint southPoint = new DataPoint(new Latitude(latRange.getMinimum(), Coordinate.FORMAT_DEG),
+                       new Longitude(lonRange.getMidValue(), Coordinate.FORMAT_DEG), null);
+               DataPoint northPoint = new DataPoint(new Latitude(latRange.getMaximum(), Coordinate.FORMAT_DEG),
+                       new Longitude(lonRange.getMidValue(), Coordinate.FORMAT_DEG), null);
+               double nsDist = Distance.convertRadiansToDistance(
+                       DataPoint.calculateRadiansBetween(northPoint, southPoint), UnitSetLibrary.UNITS_METRES); // both in m
+               // Same again for bottom and top, take maximum
+               DataPoint westPoint = new DataPoint(new Latitude(latRange.getMidValue(), Coordinate.FORMAT_DEG),
+                       new Longitude(lonRange.getMinimum(), Coordinate.FORMAT_DEG), null);
+               DataPoint eastPoint = new DataPoint(new Latitude(latRange.getMidValue(), Coordinate.FORMAT_DEG),
+                       new Longitude(lonRange.getMinimum(), Coordinate.FORMAT_DEG), null);
+               double ewDist = Distance.convertRadiansToDistance(
+                       DataPoint.calculateRadiansBetween(westPoint, eastPoint), UnitSetLibrary.UNITS_METRES); // both in m
+               final double horizDistance = Math.max(nsDist, ewDist) * BORDER_MULTIPLIER;
+
+               return horizDistance;
+       }
+}
index b4e82e34cfa8c8e1e48b2e92912f3f1f41abdbc2..a073297a09b520245c53c4cabf6f526745124742 100644 (file)
@@ -39,9 +39,20 @@ public class Unit
         * @param inSuffix suffix to name key
         */
        public Unit(Unit inParent, String inSuffix)
         * @param inSuffix suffix to name key
         */
        public Unit(Unit inParent, String inSuffix)
+       {
+               this(inParent, inSuffix, 1.0);
+       }
+
+       /**
+        * Unit constructor
+        * @param inParent parent unit
+        * @param inSuffix suffix to name key
+        * @param inFactor additional time factor to apply
+        */
+       public Unit(Unit inParent, String inSuffix, double inFactor)
        {
                _nameKey = inParent._nameKey + inSuffix;
        {
                _nameKey = inParent._nameKey + inSuffix;
-               _multFactorFromStd = inParent._multFactorFromStd;
+               _multFactorFromStd = inParent._multFactorFromStd * inFactor;
                _isStandard = inParent._isStandard;
        }
 
                _isStandard = inParent._isStandard;
        }
 
index bd53e1b98b6da732945e977981b874281ff12866..7239ead76177fde38ba9c1a8ad614a60f90c0d8d 100644 (file)
@@ -10,7 +10,6 @@ public class UnitSet
        private Unit _speedUnit = null;
        private Unit _altitudeUnit = null;
        private Unit _vertSpeedUnit = null;
        private Unit _speedUnit = null;
        private Unit _altitudeUnit = null;
        private Unit _vertSpeedUnit = null;
-       private Altitude.Format _defaultAltitudeFormat = Altitude.Format.METRES;
 
        /**
         * Constructor
 
        /**
         * Constructor
@@ -18,16 +17,17 @@ public class UnitSet
         * @param inDistanceUnit distance unit
         * @param inAltitudeUnit altitude unit
         * @param inAltitudeFormat default altitude format
         * @param inDistanceUnit distance unit
         * @param inAltitudeUnit altitude unit
         * @param inAltitudeFormat default altitude format
+        * @param inSpeedUnit unit for horizontal speeds
+        * @param inVerticalSpeedUnit unit for vertical speeds
         */
        public UnitSet(String inNameKey, Unit inDistanceUnit,
         */
        public UnitSet(String inNameKey, Unit inDistanceUnit,
-               Unit inAltitudeUnit, Altitude.Format inAltitudeFormat)
+               Unit inAltitudeUnit, Unit inSpeedUnit, Unit inVerticalSpeedUnit)
        {
                _nameKey = inNameKey;
                _distanceUnit = inDistanceUnit;
        {
                _nameKey = inNameKey;
                _distanceUnit = inDistanceUnit;
-               _speedUnit = new Unit(_distanceUnit, "perhour");
                _altitudeUnit = inAltitudeUnit;
                _altitudeUnit = inAltitudeUnit;
-               _defaultAltitudeFormat = inAltitudeFormat;
-               _vertSpeedUnit = new Unit(_altitudeUnit, "persec");
+               _speedUnit = inSpeedUnit;
+               _vertSpeedUnit = inVerticalSpeedUnit;
        }
 
        /**
        }
 
        /**
@@ -66,9 +66,14 @@ public class UnitSet
        }
 
        /**
        }
 
        /**
-        * @return default altitude format
+        * @return default point creation options for this unit set
         */
         */
-       public Altitude.Format getDefaultAltitudeFormat() {
-               return _defaultAltitudeFormat;
+       public PointCreateOptions getDefaultOptions()
+       {
+               PointCreateOptions options = new PointCreateOptions();
+               options.setAltitudeUnits(getAltitudeUnit());
+               options.setSpeedUnits(getSpeedUnit());
+               options.setVerticalSpeedUnits(getVerticalSpeedUnit(), true);
+               return options;
        }
 }
        }
 }
index b270124193a8598f3006872da4400504762a3e10..37f3cfc5f4a8f54f94eef9311ee97cf925390a4e 100644 (file)
@@ -18,11 +18,21 @@ public abstract class UnitSetLibrary
        /** Units for nautical miles */
        public static final Unit UNITS_NAUTICAL_MILES = new Unit("nauticalmiles", 1/1852.0);
 
        /** Units for nautical miles */
        public static final Unit UNITS_NAUTICAL_MILES = new Unit("nauticalmiles", 1/1852.0);
 
+       // Speed units - all conversion factors from metres per second
+       public static final Unit SPEED_UNITS_METRESPERSEC = new Unit(UNITS_METRES, "persec");
+       public static final Unit SPEED_UNITS_FEETPERSEC   = new Unit(UNITS_FEET, "persec");
+       public static final Unit SPEED_UNITS_MILESPERHOUR = new Unit(UNITS_MILES, "perhour", 60.0 * 60.0);
+       public static final Unit SPEED_UNITS_KNOTS        = new Unit(UNITS_NAUTICAL_MILES, "perhour", 60.0 * 60.0);
+       public static final Unit SPEED_UNITS_KMPERHOUR    = new Unit(UNITS_KILOMETRES, "perhour", 60.0 * 60.0);
+       public static final Unit[] ALL_SPEED_UNITS = {SPEED_UNITS_METRESPERSEC, SPEED_UNITS_KMPERHOUR,
+               SPEED_UNITS_FEETPERSEC, SPEED_UNITS_MILESPERHOUR};
+
        /** Array of available unit sets */
        /** Array of available unit sets */
-       private static UnitSet[] _sets = {
-               new UnitSet("unitset.kilometres", UNITS_KILOMETRES, UNITS_METRES, Altitude.Format.METRES),
-               new UnitSet("unitset.miles", UNITS_MILES, UNITS_FEET, Altitude.Format.FEET),
-               new UnitSet("unitset.nautical", UNITS_NAUTICAL_MILES, UNITS_FEET, Altitude.Format.FEET)
+       private static UnitSet[] _sets =
+       {
+               new UnitSet("unitset.kilometres", UNITS_KILOMETRES, UNITS_METRES, SPEED_UNITS_KMPERHOUR, SPEED_UNITS_METRESPERSEC),
+               new UnitSet("unitset.miles", UNITS_MILES, UNITS_FEET, SPEED_UNITS_MILESPERHOUR, SPEED_UNITS_FEETPERSEC),
+               new UnitSet("unitset.nautical", UNITS_NAUTICAL_MILES, UNITS_FEET, SPEED_UNITS_KNOTS, SPEED_UNITS_FEETPERSEC)
        };
 
        /**
        };
 
        /**
@@ -53,9 +63,8 @@ public abstract class UnitSetLibrary
        public static UnitSet getUnitSet(String inKey)
        {
                // Loop over all available unit sets
        public static UnitSet getUnitSet(String inKey)
        {
                // Loop over all available unit sets
-               for (int i=0; i<getNumUnitSets(); i++)
+               for (UnitSet set : _sets)
                {
                {
-                       UnitSet set = getUnitSet(i);
                        if (set.getNameKey().equals(inKey)) {
                                return set;
                        }
                        if (set.getNameKey().equals(inKey)) {
                                return set;
                        }
index 0ec7ae6fc06ad1063531f605eff95ea2fe47054d..7a95693b734dcf0ae67bdd19c27b202ede513c6f 100644 (file)
@@ -99,7 +99,7 @@ public class AboutScreen extends GenericFunction
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.languages")).append(" : ")
                        .append("\u010de\u0161tina, deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano, magyar,<br>" +
                                " nederlands, polski, portugu\u00EAs, \u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian), \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese),<br>" +
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.languages")).append(" : ")
                        .append("\u010de\u0161tina, deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano, magyar,<br>" +
                                " nederlands, polski, portugu\u00EAs, \u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian), \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese),<br>" +
-                               " \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, rom\u00E2n\u0103, afrikaans, bahasa indonesia</p>");
+                               " \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, afrikaans, rom\u00E2n\u0103</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.translatedby")).append("</p>");
                JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
                descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.translatedby")).append("</p>");
                JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
                descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
index 85c52dee7f5291bc8293be3aa3b5038a0068a150..c98ab3e17843a27db638d74ffac802c5400bea36 100644 (file)
@@ -19,8 +19,9 @@ import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.Field;
 import tim.prune.data.Field;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
 
 /**
  * Class to provide the function to add an altitude offset to a track range
 
 /**
  * Class to provide the function to add an altitude offset to a track range
@@ -31,7 +32,7 @@ public class AddAltitudeOffset extends GenericFunction
        private JLabel _descLabel = null;
        private JTextField _editField = null;
        private JButton _okButton = null;
        private JLabel _descLabel = null;
        private JTextField _editField = null;
        private JButton _okButton = null;
-       private Altitude.Format _altFormat = Altitude.Format.NO_FORMAT;
+       private Unit _altUnit = null;
 
 
        /**
 
 
        /**
@@ -152,11 +153,11 @@ public class AddAltitudeOffset extends GenericFunction
         */
        private void setLabelText()
        {
         */
        private void setLabelText()
        {
-               _altFormat = Altitude.Format.FEET;
+               _altUnit = UnitSetLibrary.UNITS_FEET;
                if (Config.getUnitSet().getAltitudeUnit().isStandard()) {
                if (Config.getUnitSet().getAltitudeUnit().isStandard()) {
-                       _altFormat = Altitude.Format.METRES;
+                       _altUnit = UnitSetLibrary.UNITS_METRES;
                }
                }
-               final String unitKey = (_altFormat==Altitude.Format.METRES?"units.metres.short":"units.feet.short");
+               final String unitKey = _altUnit.getShortnameKey();
                _descLabel.setText(I18nManager.getText("dialog.addaltitude.desc") + " (" + I18nManager.getText(unitKey) + ")");
        }
 
                _descLabel.setText(I18nManager.getText("dialog.addaltitude.desc") + " (" + I18nManager.getText(unitKey) + ")");
        }
 
@@ -166,7 +167,7 @@ public class AddAltitudeOffset extends GenericFunction
        private void finish()
        {
                // Pass information back to App to complete function
        private void finish()
        {
                // Pass information back to App to complete function
-               _app.finishAddAltitudeOffset(_editField.getText(), _altFormat);
+               _app.finishAddAltitudeOffset(_editField.getText(), _altUnit);
                _dialog.dispose();
        }
 }
                _dialog.dispose();
        }
 }
index da1f15e8a951e0d6c258041e5872d5e84c799299..52a8874cf100b355b8b0d6acac60215dcfe29202 100644 (file)
@@ -105,9 +105,21 @@ public class AddMapSourceDialog
                KeyAdapter keyListener = new KeyAdapter() {
                        public void keyReleased(KeyEvent e) {
                                super.keyReleased(e);
                KeyAdapter keyListener = new KeyAdapter() {
                        public void keyReleased(KeyEvent e) {
                                super.keyReleased(e);
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _addDialog.dispose();
+                               }
+                               else {
+                                       enableOK();
+                               }
+                       }
+               };
+               // Listener for any gui changes (to enable ok when anything changes on an edit)
+               ActionListener okEnabler = new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
                                enableOK();
                        }
                };
                                enableOK();
                        }
                };
+
                // openstreetmap panel
                JPanel osmPanel = new JPanel();
                osmPanel.setLayout(new BorderLayout());
                // openstreetmap panel
                JPanel osmPanel = new JPanel();
                osmPanel.setLayout(new BorderLayout());
@@ -144,6 +156,8 @@ public class AddMapSourceDialog
                        radioGroup.add(_baseTypeRadios[i]);
                        c.gridx = 2+i; c.weightx = 0.0;
                        gbPanel.add(_baseTypeRadios[i], c);
                        radioGroup.add(_baseTypeRadios[i]);
                        c.gridx = 2+i; c.weightx = 0.0;
                        gbPanel.add(_baseTypeRadios[i], c);
+                       // Each type radio needs listener to call enableOk()
+                       _baseTypeRadios[i].addActionListener(okEnabler);
                }
 
                // Top layer
                }
 
                // Top layer
@@ -161,6 +175,8 @@ public class AddMapSourceDialog
                        radioGroup.add(_topTypeRadios[i]);
                        c.gridx = 2+i; c.weightx = 0.0;
                        gbPanel.add(_topTypeRadios[i], c);
                        radioGroup.add(_topTypeRadios[i]);
                        c.gridx = 2+i; c.weightx = 0.0;
                        gbPanel.add(_topTypeRadios[i], c);
+                       // Each type radio needs listener to call enableOk()
+                       _topTypeRadios[i].addActionListener(okEnabler);
                }
                // Max zoom
                c.gridx = 0; c.gridy = 3;
                }
                // Max zoom
                c.gridx = 0; c.gridy = 3;
@@ -169,6 +185,8 @@ public class AddMapSourceDialog
                for (int i=10; i<=20; i++) {
                        _oZoomCombo.addItem("" + i);
                }
                for (int i=10; i<=20; i++) {
                        _oZoomCombo.addItem("" + i);
                }
+               // zoom dropdown needs listener to call enableOk()
+               _oZoomCombo.addActionListener(okEnabler);
                c.gridx = 1;
                gbPanel.add(_oZoomCombo, c);
                osmPanel.add(gbPanel, BorderLayout.NORTH);
                c.gridx = 1;
                gbPanel.add(_oZoomCombo, c);
                osmPanel.add(gbPanel, BorderLayout.NORTH);
index e3d362738d6814c8fc99cf6c86ec20baaaace679..76c69da7f928aa3099e383c5603a06de11997025 100644 (file)
@@ -243,7 +243,7 @@ public class DownloadOsmFunction extends GenericFunction implements Runnable
         */
        public void run()
        {
         */
        public void run()
        {
-               final String url = "http://xapi.openstreetmap.org/api/0.6/map?bbox=" +
+               final String url = "http://www.overpass-api.de/api/xapi?map?bbox=" +
                        _latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
                        _latLonLabels[2].getText() + "," + _latLonLabels[0].getText();
 
                        _latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
                        _latLonLabels[2].getText() + "," + _latLonLabels[0].getText();
 
index 5b12125b0d20f34bae4776f3cefdce0ea41b6400..9e92e62158573d624b5f24c409c8ccdb56954544 100644 (file)
@@ -9,7 +9,7 @@ import tim.prune.GenericFunction;
 public abstract class Export3dFunction extends GenericFunction
 {
        /** altitude exaggeration factor */
 public abstract class Export3dFunction extends GenericFunction
 {
        /** altitude exaggeration factor */
-       protected double _altFactor = 50.0;
+       protected double _altFactor = 5.0;
 
        /**
         * Required constructor
 
        /**
         * Required constructor
index a0bae6517a09419dd5af77622f49b983de0e601e..d9dbb7011253fdb4f1ac54008739259a8eb3eab5 100644 (file)
@@ -8,7 +8,6 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
-import java.text.NumberFormat;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
@@ -20,9 +19,7 @@ import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
-import tim.prune.data.AltitudeRange;
-import tim.prune.data.DataPoint;
+import tim.prune.data.RangeStats;
 import tim.prune.data.Selection;
 import tim.prune.data.Unit;
 import tim.prune.gui.DisplayUtils;
 import tim.prune.data.Selection;
 import tim.prune.data.Unit;
 import tim.prune.gui.DisplayUtils;
@@ -63,10 +60,6 @@ public class FullRangeDetails extends GenericFunction
        /** Labels for vertical speed */
        private JLabel _totalVertSpeedLabel, _movingVertSpeedLabel = null;
 
        /** Labels for vertical speed */
        private JLabel _totalVertSpeedLabel, _movingVertSpeedLabel = null;
 
-       /** Number formatter for one decimal place */
-       private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
-       /** Flexible number formatter for different decimal places */
-       private NumberFormat _distanceFormatter = NumberFormat.getInstance();
 
        /**
         * Constructor
 
        /**
         * Constructor
@@ -75,8 +68,6 @@ public class FullRangeDetails extends GenericFunction
        public FullRangeDetails(App inApp)
        {
                super(inApp);
        public FullRangeDetails(App inApp)
        {
                super(inApp);
-               FORMAT_ONE_DP.setMaximumFractionDigits(1);
-               FORMAT_ONE_DP.setMinimumFractionDigits(1);
        }
 
        /** Get the name key */
        }
 
        /** Get the name key */
@@ -250,11 +241,14 @@ public class FullRangeDetails extends GenericFunction
        private void updateDetails()
        {
                Selection selection = _app.getTrackInfo().getSelection();
        private void updateDetails()
        {
                Selection selection = _app.getTrackInfo().getSelection();
+               // Do the calculations with a separate class
+               RangeStats stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+
                // Number of points
                // Number of points
-               _numPointsLabel.setText("" + (selection.getEnd()-selection.getStart()+1));
+               _numPointsLabel.setText("" + stats.getNumPoints());
                // Number of segments
                // Number of segments
-               _numSegsLabel.setText("" + selection.getNumSegments());
-               final boolean isMultiSegments = (selection.getNumSegments() > 1);
+               _numSegsLabel.setText("" + stats.getNumSegments());
+               final boolean isMultiSegments = (stats.getNumSegments() > 1);
                // Set visibility of third column accordingly
                _movingDistanceLabel.setVisible(isMultiSegments);
                _movingDurationLabel.setVisible(isMultiSegments);
                // Set visibility of third column accordingly
                _movingDistanceLabel.setVisible(isMultiSegments);
                _movingDurationLabel.setVisible(isMultiSegments);
@@ -265,132 +259,78 @@ public class FullRangeDetails extends GenericFunction
                _movingGradientLabel.setVisible(isMultiSegments);
                _movingVertSpeedLabel.setVisible(isMultiSegments);
 
                _movingGradientLabel.setVisible(isMultiSegments);
                _movingVertSpeedLabel.setVisible(isMultiSegments);
 
-               // Distance in current units
+               // Total and moving distance in current units
                final Unit distUnit = Config.getUnitSet().getDistanceUnit();
                final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
                final Unit distUnit = Config.getUnitSet().getDistanceUnit();
                final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
-               final double selectionDistance = selection.getDistance();
-               _totalDistanceLabel.setText(roundedNumber(selectionDistance) + " " + distUnitsStr);
+               _totalDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getTotalDistance()) + " " + distUnitsStr);
+               _movingDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getMovingDistance()) + " " + distUnitsStr);
 
                // Duration
 
                // Duration
-               long numSecs = selection.getNumSeconds();
-               _totalDurationLabel.setText(DisplayUtils.buildDurationString(numSecs));
+               _totalDurationLabel.setText(DisplayUtils.buildDurationString(stats.getTotalDurationInSeconds()));
+               _movingDurationLabel.setText(DisplayUtils.buildDurationString(stats.getMovingDurationInSeconds()));
+
                // Climb and descent
                final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
                final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
                // Climb and descent
                final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
                final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
-               if (selection.getAltitudeRange().hasRange()) {
-                       _totalClimbLabel.setText(selection.getAltitudeRange().getClimb(altUnit) + altUnitsStr);
-                       _totalDescentLabel.setText(selection.getAltitudeRange().getDescent(altUnit) + altUnitsStr);
+               if (stats.getTotalAltitudeRange().hasRange()) {
+                       _totalClimbLabel.setText(stats.getTotalAltitudeRange().getClimb(altUnit) + altUnitsStr);
+                       _totalDescentLabel.setText(stats.getTotalAltitudeRange().getDescent(altUnit) + altUnitsStr);
                }
                else {
                        _totalClimbLabel.setText("");
                        _totalDescentLabel.setText("");
                }
                }
                else {
                        _totalClimbLabel.setText("");
                        _totalDescentLabel.setText("");
                }
+               if (stats.getMovingAltitudeRange().hasRange()) {
+                       _movingClimbLabel.setText(stats.getMovingAltitudeRange().getClimb(altUnit) + altUnitsStr);
+                       _movingDescentLabel.setText(stats.getMovingAltitudeRange().getDescent(altUnit) + altUnitsStr);
+               }
+               else {
+                       _movingClimbLabel.setText("");
+                       _movingDescentLabel.setText("");
+               }
 
                // Overall pace and speed
                final String speedUnitsStr = I18nManager.getText(Config.getUnitSet().getSpeedUnit().getShortnameKey());
 
                // Overall pace and speed
                final String speedUnitsStr = I18nManager.getText(Config.getUnitSet().getSpeedUnit().getShortnameKey());
-               if (numSecs > 0 && selectionDistance > 0)
+               long numSecs = stats.getTotalDurationInSeconds();
+               double dist = stats.getTotalDistance();
+               if (numSecs > 0 && dist > 0)
                {
                {
-                       _totalPaceLabel.setText(
-                               DisplayUtils.buildDurationString((long) (numSecs/selectionDistance))
+                       _totalSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
+                       _totalPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
                                + " / " + distUnitsStr);
                                + " / " + distUnitsStr);
-                       _totalSpeedLabel.setText(roundedNumber(selectionDistance/numSecs*3600.0)
-                               + " " + speedUnitsStr);
                }
                else {
                }
                else {
-                       _totalPaceLabel.setText("");
                        _totalSpeedLabel.setText("");
                        _totalSpeedLabel.setText("");
+                       _totalPaceLabel.setText("");
                }
                }
-
-               // Moving distance
-               double movingDist = selection.getMovingDistance();
-               _movingDistanceLabel.setText(roundedNumber(movingDist) + " " + distUnitsStr);
-               // Moving average speed
-               long numMovingSecs = selection.getMovingSeconds();
-               if (numMovingSecs > 0)
+               // and same for within the segments
+               numSecs = stats.getMovingDurationInSeconds();
+               dist = stats.getMovingDistance();
+               if (numSecs > 0 && dist > 0)
                {
                {
-                       _movingDurationLabel.setText(DisplayUtils.buildDurationString(numMovingSecs));
-                       _movingSpeedLabel.setText(roundedNumber(movingDist/numMovingSecs*3600.0)
-                               + " " + speedUnitsStr);
-                       _movingPaceLabel.setText(
-                               DisplayUtils.buildDurationString((long) (numMovingSecs/movingDist))
+                       _movingSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
+                       _movingPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
                                + " / " + distUnitsStr);
                }
                                + " / " + distUnitsStr);
                }
-               else
-               {
-                       _movingDurationLabel.setText("");
+               else {
                        _movingSpeedLabel.setText("");
                        _movingPaceLabel.setText("");
                }
 
                        _movingSpeedLabel.setText("");
                        _movingPaceLabel.setText("");
                }
 
-               // Moving gradient and moving climb/descent
-               Altitude firstAlt = null, lastAlt = null;
-               Altitude veryFirstAlt = null, veryLastAlt = null;
-               AltitudeRange altRange = new AltitudeRange();
-               double movingHeightDiff = 0.0;
-               if (movingDist > 0.0)
-               {
-                       for (int pNum = selection.getStart(); pNum <= selection.getEnd(); pNum++)
-                       {
-                               DataPoint p = _app.getTrackInfo().getTrack().getPoint(pNum);
-                               if (p != null && !p.isWaypoint())
-                               {
-                                       // If we're starting a new segment, calculate the height diff of the previous one
-                                       if (p.getSegmentStart())
-                                       {
-                                               if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
-                                                       movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
-                                               firstAlt = null; lastAlt = null;
-                                       }
-                                       Altitude alt = p.getAltitude();
-                                       if (alt != null && alt.isValid())
-                                       {
-                                               if (firstAlt == null) firstAlt = alt;
-                                               else lastAlt = alt;
-                                               if (veryFirstAlt == null) veryFirstAlt = alt;
-                                               else veryLastAlt = alt;
-                                       }
-                                       // Keep track of climb and descent too
-                                       if (p.getSegmentStart())
-                                               altRange.ignoreValue(alt);
-                                       else
-                                               altRange.addValue(alt);
-                               }
-                       }
-                       // deal with last segment
-                       if (firstAlt != null && firstAlt.isValid() && lastAlt != null && lastAlt.isValid())
-                               movingHeightDiff = movingHeightDiff + lastAlt.getMetricValue() - firstAlt.getMetricValue();
-                       final double metricMovingDist = movingDist / distUnit.getMultFactorFromStd(); // convert back to metres
-                       final double gradient = movingHeightDiff * 100.0 / metricMovingDist;
-                       _movingGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
-               }
-               if (!altRange.hasRange()) {
-                       _movingGradientLabel.setText("");
-               }
-               final boolean hasAltitudes = veryFirstAlt != null && veryFirstAlt.isValid() && veryLastAlt != null && veryLastAlt.isValid();
-
-               // Total gradient
-               final double metreDist = selection.getDistance() / distUnit.getMultFactorFromStd(); // convert back to metres
-               if (hasAltitudes && metreDist > 0.0)
-               {
-                       // got an altitude and range
-                       int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
-                       double gradient = altDiffInMetres * 100.0 / metreDist;
-                       _totalGradientLabel.setText(FORMAT_ONE_DP.format(gradient) + " %");
+               // Gradient
+               if (stats.getTotalAltitudeRange().hasRange()) {
+                       _totalGradientLabel.setText(DisplayUtils.formatOneDp(stats.getTotalGradient()) + " %");
                }
                else {
                }
                else {
-                       // no altitude given
                        _totalGradientLabel.setText("");
                }
                        _totalGradientLabel.setText("");
                }
-
-               // Moving climb/descent
-               if (altRange.hasRange()) {
-                       _movingClimbLabel.setText(altRange.getClimb(altUnit) + altUnitsStr);
-                       _movingDescentLabel.setText(altRange.getDescent(altUnit) + altUnitsStr);
+               if (stats.getMovingAltitudeRange().hasRange()) {
+                       _movingGradientLabel.setText(DisplayUtils.formatOneDp(stats.getMovingGradient()) + " %");
                }
                else {
                }
                else {
-                       _movingClimbLabel.setText("");
-                       _movingDescentLabel.setText("");
+                       _movingGradientLabel.setText("");
                }
                }
+
                // Maximum speed
                SpeedData speeds = new SpeedData(_app.getTrackInfo().getTrack());
                speeds.init(Config.getUnitSet());
                // Maximum speed
                SpeedData speeds = new SpeedData(_app.getTrackInfo().getTrack());
                speeds.init(Config.getUnitSet());
@@ -402,7 +342,7 @@ public class FullRangeDetails extends GenericFunction
                        }
                }
                if (maxSpeed > 0.0) {
                        }
                }
                if (maxSpeed > 0.0) {
-                       _maxSpeedLabel.setText(roundedNumber(maxSpeed) + " " + speedUnitsStr);
+                       _maxSpeedLabel.setText(DisplayUtils.roundedNumber(maxSpeed) + " " + speedUnitsStr);
                }
                else {
                        _maxSpeedLabel.setText("");
                }
                else {
                        _maxSpeedLabel.setText("");
@@ -410,40 +350,17 @@ public class FullRangeDetails extends GenericFunction
 
                // vertical speed
                final String vertSpeedUnitsStr = I18nManager.getText(Config.getUnitSet().getVerticalSpeedUnit().getShortnameKey());
 
                // vertical speed
                final String vertSpeedUnitsStr = I18nManager.getText(Config.getUnitSet().getVerticalSpeedUnit().getShortnameKey());
-               if (hasAltitudes && metreDist > 0.0 && numSecs > 0)
+               if (stats.getMovingAltitudeRange().hasRange() && stats.getTotalDurationInSeconds() > 0)
                {
                {
-                       // got an altitude and time - do total
-                       final int altDiffInMetres = veryLastAlt.getValue(Altitude.Format.METRES) - veryFirstAlt.getValue(Altitude.Format.METRES);
-                       final double altDiff = altDiffInMetres * altUnit.getMultFactorFromStd();
-                       _totalVertSpeedLabel.setText(roundedNumber(altDiff/numSecs) + " " + vertSpeedUnitsStr);
-                       // and moving
-                       _movingVertSpeedLabel.setText(roundedNumber(movingHeightDiff * altUnit.getMultFactorFromStd() / numMovingSecs) + " " + vertSpeedUnitsStr);
+                       // got an altitude and time - do totals
+                       _totalVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getTotalVerticalSpeed()) + " " + vertSpeedUnitsStr);
+                       _movingVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getMovingVerticalSpeed()) + " " + vertSpeedUnitsStr);
                }
                }
-               else {
+               else
+               {
                        // no vertical speed available
                        _totalVertSpeedLabel.setText("");
                        _movingVertSpeedLabel.setText("");
                }
        }
                        // no vertical speed available
                        _totalVertSpeedLabel.setText("");
                        _movingVertSpeedLabel.setText("");
                }
        }
-
-       /**
-        * Format a number to a sensible precision
-        * @param inDist distance
-        * @return formatted String
-        */
-       private String roundedNumber(double inDist)
-       {
-               // Set precision of formatter
-               int numDigits = 0;
-               if (inDist < 1.0)
-                       numDigits = 3;
-               else if (inDist < 10.0)
-                       numDigits = 2;
-               else if (inDist < 100.0)
-                       numDigits = 1;
-               // set formatter
-               _distanceFormatter.setMaximumFractionDigits(numDigits);
-               _distanceFormatter.setMinimumFractionDigits(numDigits);
-               return _distanceFormatter.format(inDist);
-       }
 }
 }
index 0e6d9eb5ae432454b505cc8fb8ba4a200b2bf57c..4c11bf9d13d4e0b393604939e1535d633499204f 100644 (file)
@@ -71,21 +71,48 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
                        lat = (coords[0] + coords[2]) / 2.0;
                        lon = (coords[1] + coords[3]) / 2.0;
                }
                        lat = (coords[0] + coords[2]) / 2.0;
                        lon = (coords[1] + coords[3]) / 2.0;
                }
-               else {
+               else
+               {
                        lat = point.getLatitude().getDouble();
                        lon = point.getLongitude().getDouble();
                }
 
                        lat = point.getLatitude().getDouble();
                        lon = point.getLongitude().getDouble();
                }
 
-               String descMessage = "";
-               InputStream inStream = null;
+               // Firstly try the local language
+               String lang = I18nManager.getText("wikipedia.lang");
+               submitSearch(lat, lon, lang);
+               // If we didn't get anything, try a secondary language
+               if (_trackListModel.isEmpty() && _errorMessage == null && lang.equals("als")) {
+                       submitSearch(lat, lon, "de");
+               }
+               // If still nothing then try english
+               if (_trackListModel.isEmpty() && _errorMessage == null && !lang.equals("en")) {
+                       submitSearch(lat, lon, "en");
+               }
 
 
+               // Set status label according to error or "none found", leave blank if ok
+               if (_errorMessage == null && _trackListModel.isEmpty()) {
+                       _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
+               }
+               _statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
+       }
+
+       /**
+        * Submit the search for the given parameters
+        * @param inLat latitude
+        * @param inLon longitude
+        * @param inLang language code to use, such as en or de
+        */
+       private void submitSearch(double inLat, double inLon, String inLang)
+       {
                // Example http://api.geonames.org/findNearbyWikipedia?lat=47&lng=9
                String urlString = "http://api.geonames.org/findNearbyWikipedia?lat=" +
                // Example http://api.geonames.org/findNearbyWikipedia?lat=47&lng=9
                String urlString = "http://api.geonames.org/findNearbyWikipedia?lat=" +
-                       lat + "&lng=" + lon + "&maxRows=" + MAX_RESULTS
-                       + "&radius=" + MAX_DISTANCE + "&lang=" + I18nManager.getText("wikipedia.lang")
+                       inLat + "&lng=" + inLon + "&maxRows=" + MAX_RESULTS
+                       + "&radius=" + MAX_DISTANCE + "&lang=" + inLang
                        + "&username=" + GEONAMES_USERNAME;
                // Parse the returned XML with a special handler
                GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
                        + "&username=" + GEONAMES_USERNAME;
                // Parse the returned XML with a special handler
                GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+               InputStream inStream = null;
+
                try
                {
                        URL url = new URL(urlString);
                try
                {
                        URL url = new URL(urlString);
@@ -94,7 +121,7 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
                        saxParser.parse(inStream, xmlHandler);
                }
                catch (Exception e) {
                        saxParser.parse(inStream, xmlHandler);
                }
                catch (Exception e) {
-                       descMessage = e.getClass().getName() + " - " + e.getMessage();
+                       _errorMessage = e.getClass().getName() + " - " + e.getMessage();
                }
                // Close stream and ignore errors
                try {
                }
                // Close stream and ignore errors
                try {
@@ -104,21 +131,18 @@ public class GetWikipediaFunction extends GenericDownloaderFunction
                ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
                _trackListModel.addTracks(trackList);
 
                ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
                _trackListModel.addTracks(trackList);
 
-               // Set status label according to error or "none found", leave blank if ok
-               if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
-                       descMessage = I18nManager.getText("dialog.gpsies.nonefound");
-               }
-               _statusLabel.setText(descMessage);
                // Show error message if any
                // Show error message if any
-               if (trackList == null || trackList.size() == 0) {
+               if (_trackListModel.isEmpty())
+               {
                        String error = xmlHandler.getErrorMessage();
                        String error = xmlHandler.getErrorMessage();
-                       if (error != null && !error.equals("")) {
+                       if (error != null && !error.equals(""))
+                       {
                                _app.showErrorMessageNoLookup(getNameKey(), error);
                                _app.showErrorMessageNoLookup(getNameKey(), error);
+                               _errorMessage = error;
                        }
                }
        }
 
                        }
                }
        }
 
-
        /**
         * Load the selected point(s)
         */
        /**
         * Load the selected point(s)
         */
index 7587949b25c893e5a4c9565d69d6a723ae603ec2..0228014deb4810f025ba3d340247fe4bc920004b 100644 (file)
@@ -29,6 +29,8 @@ import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
 import tim.prune.data.Field;
 import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.GuiGridLayout;
 
 /**
 import tim.prune.gui.GuiGridLayout;
 
 /**
@@ -75,7 +77,7 @@ public class PasteCoordinates extends GenericFunction
                // MAYBE: Paste clipboard into the edit field
                _coordField.setText("");
                _nameField.setText("");
                // MAYBE: Paste clipboard into the edit field
                _coordField.setText("");
                _nameField.setText("");
-               boolean useMetres = (Config.getUnitSet().getDefaultAltitudeFormat() == Altitude.Format.METRES);
+               boolean useMetres = (Config.getUnitSet().getAltitudeUnit() == UnitSetLibrary.UNITS_METRES);
                _altUnitsDropDown.setSelectedIndex(useMetres?0:1);
                enableOK();
                _dialog.setVisible(true);
                _altUnitsDropDown.setSelectedIndex(useMetres?0:1);
                enableOK();
                _dialog.setVisible(true);
@@ -236,9 +238,9 @@ public class PasteCoordinates extends GenericFunction
                if (inValue3 != null)
                {
                        // Look at altitude units dropdown
                if (inValue3 != null)
                {
                        // Look at altitude units dropdown
-                       final Altitude.Format altFormat = (_altUnitsDropDown.getSelectedIndex()==0?
-                               Altitude.Format.METRES:Altitude.Format.FEET);
-                       alt = new Altitude(inValue3, altFormat);
+                       final Unit altUnit = (_altUnitsDropDown.getSelectedIndex()==0?
+                               UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET);
+                       alt = new Altitude(inValue3, altUnit);
                        if (!alt.isValid()) {alt = null;}
                }
                // See if value1 can be lat and value2 lon:
                        if (!alt.isValid()) {alt = null;}
                }
                // See if value1 can be lat and value2 lon:
index 0458943036ed8007ffecb0838bf66c504aff4e6d..5712983394e24882d7b1098a1eeead49d92f41e8 100644 (file)
@@ -67,7 +67,7 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
                        JOptionPane.QUESTION_MESSAGE, null, null, "");
                if (search != null)
                {
                        JOptionPane.QUESTION_MESSAGE, null, null, "");
                if (search != null)
                {
-                       _searchTerm = search.toString();
+                       _searchTerm = search.toString().toLowerCase();
                        if (!_searchTerm.equals("")) {
                                super.begin();
                        }
                        if (!_searchTerm.equals("")) {
                                super.begin();
                        }
@@ -81,29 +81,43 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
        {
                _statusLabel.setText(I18nManager.getText("confirm.running"));
 
        {
                _statusLabel.setText(I18nManager.getText("confirm.running"));
 
-               String descMessage = "";
-               InputStream inStream = null;
+               // Replace awkward characters with character equivalents
+               final String searchTerm = encodeSearchTerm(_searchTerm);
 
 
-               // language (only de and en available)
+               // Firstly try the local language
                String lang = I18nManager.getText("wikipedia.lang");
                String lang = I18nManager.getText("wikipedia.lang");
-               if (lang.equals("de") || lang.equals("als")) {
-                       lang = "de";
+               submitSearch(searchTerm, lang);
+               // If we didn't get anything, try a secondary language
+               if (_trackListModel.isEmpty() && _errorMessage == null && lang.equals("als")) {
+                       submitSearch(searchTerm, "de");
                }
                }
-               else {
-                       lang = "en";
+               // If still nothing then try english
+               if (_trackListModel.isEmpty() && _errorMessage == null && !lang.equals("en")) {
+                       submitSearch(searchTerm, "en");
                }
                }
-               // Replace awkward characters with character equivalents
-               String searchTerm;
-               try {
-                       searchTerm = URLEncoder.encode(_searchTerm, "UTF-8");
-               } catch (UnsupportedEncodingException e1) {
-                       searchTerm = _searchTerm;
+
+               // Set status label according to error or "none found", leave blank if ok
+               if (_errorMessage == null && _trackListModel.isEmpty()) {
+                       _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
                }
                }
-               // Example http://ws.geonames.org/wikipediaSearch?q=london&maxRows=10
-               String urlString = "http://api.geonames.org/wikipediaSearch?title=" + searchTerm
-                       + "&maxRows=" + MAX_RESULTS + "&lang=" + lang + "&username=" + GEONAMES_USERNAME;
+               _statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
+       }
+
+       /**
+        * Submit the given search to the server
+        * @param inSearchTerm search term
+        * @param inLang language code such as en, de
+        */
+       private void submitSearch(String inSearchTerm, String inLang)
+       {
+               // System.out.println("Searching for '" + inSearchTerm + "' with lang: " + inLang);
+
+               String urlString = "http://api.geonames.org/wikipediaSearch?title=" + inSearchTerm
+                       + "&maxRows=" + MAX_RESULTS + "&lang=" + inLang + "&username=" + GEONAMES_USERNAME;
                // Parse the returned XML with a special handler
                GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
                // Parse the returned XML with a special handler
                GetWikipediaXmlHandler xmlHandler = new GetWikipediaXmlHandler();
+               InputStream inStream = null;
+
                try
                {
                        URL url = new URL(urlString);
                try
                {
                        URL url = new URL(urlString);
@@ -112,7 +126,7 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
                        saxParser.parse(inStream, xmlHandler);
                }
                catch (Exception e) {
                        saxParser.parse(inStream, xmlHandler);
                }
                catch (Exception e) {
-                       descMessage = e.getClass().getName() + " - " + e.getMessage();
+                       _errorMessage = e.getClass().getName() + " - " + e.getMessage();
                }
                // Close stream and ignore errors
                try {
                }
                // Close stream and ignore errors
                try {
@@ -122,15 +136,59 @@ public class SearchWikipediaNames extends GenericDownloaderFunction
                ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
                // TODO: Do a better job of sorting replies by relevance - use three different lists
                _trackListModel.addTracks(trackList);
                ArrayList<GpsiesTrack> trackList = xmlHandler.getTrackList();
                // TODO: Do a better job of sorting replies by relevance - use three different lists
                _trackListModel.addTracks(trackList);
+       }
 
 
-               // Set status label according to error or "none found", leave blank if ok
-               if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
-                       descMessage = I18nManager.getText("dialog.gpsies.nonefound");
+       /**
+        * Replace tricky characters in the search term and encode the others
+        * @param inSearchTerm entered search term
+        * @return modified search term to give to geonames
+        */
+       private static final String encodeSearchTerm(String inSearchTerm)
+       {
+               if (inSearchTerm != null && inSearchTerm.length() > 0)
+               {
+                       // Replace umlauts oe, ue, ae, OE, UE, AE, szlig
+                       StringBuilder sb = new StringBuilder();
+                       final int numChars = inSearchTerm.length();
+                       for (int i=0; i<numChars; i++)
+                       {
+                               char c = inSearchTerm.charAt(i);
+                               switch (c)
+                               {
+                                       // German umlauted vowels, add an "e"
+                                       case '\u00fc' : sb.append("ue"); break;
+                                       case '\u00e4' : sb.append("ae"); break;
+                                       case '\u00f6' : sb.append("oe"); break;
+                                       // German doppel s
+                                       case '\u00df' : sb.append("ss"); break;
+                                       // accented vowels
+                                       case '\u00e8' : case '\u00e9' :
+                                       case '\u00ea' : case '\u00eb' : sb.append('e'); break;
+                                       case '\u00e0' : case '\u00e1' :
+                                       case '\u00e2' : sb.append('a'); break;
+                                       case '\u00f2' : case '\u00f3' :
+                                       case '\u00f4' : sb.append('o'); break;
+                                       case '\u00ec' : case '\u00ed' :
+                                       case '\u00ee' : case '\u00ef' : sb.append('i'); break;
+                                       // cedillas, ny, l bar
+                                       case '\u00e7' : sb.append('c'); break;
+                                       case '\u015f' : sb.append('s'); break;
+                                       case '\u00f1' : sb.append('n'); break;
+                                       case '\u0142' : sb.append('l'); break;
+                                       // everything else
+                                       default  : sb.append(c);
+                               }
+                       }
+                       String searchTerm = inSearchTerm;
+                       try {
+                               searchTerm = URLEncoder.encode(sb.toString(), "UTF-8");
+                       } catch (UnsupportedEncodingException e1) {}
+                       // System.out.println("Converted '" + inSearchTerm + "' to '" + searchTerm + "'");
+                       return searchTerm;
                }
                }
-               _statusLabel.setText(descMessage);
+               return "";
        }
 
        }
 
-
        /**
         * Load the selected point(s)
         */
        /**
         * Load the selected point(s)
         */
diff --git a/tim/prune/function/SetKmzImageSize.java b/tim/prune/function/SetKmzImageSize.java
deleted file mode 100644 (file)
index c63d4fe..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-package tim.prune.function;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.SwingConstants;
-
-import tim.prune.App;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
-import tim.prune.config.Config;
-import tim.prune.gui.WholeNumberField;
-
-/**
- * Class to provide the function to set the image size for kmz output
- */
-public class SetKmzImageSize extends GenericFunction
-{
-       private JDialog _dialog = null;
-       private JButton _okButton = null;
-       private WholeNumberField _widthField = null, _heightField = null;
-
-
-       /**
-        * Constructor
-        * @param inApp application object for callback
-        */
-       public SetKmzImageSize(App inApp)
-       {
-               super(inApp);
-       }
-
-       /** Get the name key */
-       public String getNameKey() {
-               return "function.setkmzimagesize";
-       }
-
-       /**
-        * Begin the function
-        */
-       public void begin()
-       {
-               // Make dialog window
-               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();
-               }
-               // Initialise values from config
-               _widthField.setValue(Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH));
-               _heightField.setValue(Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT));
-               _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());
-
-               // Make a central panel with the text boxes
-               JPanel mainPanel = new JPanel();
-               mainPanel.setLayout(new GridLayout(0, 2));
-               mainPanel.add(makeRightLabel("dialog.saveconfig.prune.kmzimagewidth"));
-               _widthField = new WholeNumberField(4);
-               mainPanel.add(_widthField);
-               mainPanel.add(makeRightLabel("dialog.saveconfig.prune.kmzimageheight"));
-               _heightField = new WholeNumberField(4);
-               mainPanel.add(_heightField);
-               dialogPanel.add(mainPanel, BorderLayout.CENTER);
-
-               // Listeners to enable/disable ok button
-               KeyAdapter keyListener = new KeyAdapter() {
-                       /** Key released */
-                       public void keyReleased(KeyEvent arg0) {
-                               _okButton.setEnabled(_widthField.getValue()>0 && _heightField.getValue()>0);
-                       }
-               };
-               MouseAdapter mouseListener = new MouseAdapter() {
-                       public void mouseReleased(java.awt.event.MouseEvent arg0) {
-                               _okButton.setEnabled(_widthField.getValue()>0 && _heightField.getValue()>0);
-                       };
-               };
-               _widthField.addKeyListener(keyListener);
-               _heightField.addKeyListener(keyListener);
-               _widthField.addMouseListener(mouseListener);
-               _heightField.addMouseListener(mouseListener);
-
-               // button panel at bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               _okButton = new JButton(I18nManager.getText("button.ok"));
-               ActionListener okListener = new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               finish();
-                       }
-               };
-               _okButton.addActionListener(okListener);
-               _okButton.setEnabled(false);
-               buttonPanel.add(_okButton);
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _dialog.dispose();
-                       }
-               });
-               buttonPanel.add(cancelButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
-               return dialogPanel;
-       }
-
-       /**
-        * @param inKey text key
-        * @return right-aligned label
-        */
-       private static final JLabel makeRightLabel(String inKey)
-       {
-               JLabel label = new JLabel(I18nManager.getText(inKey) + " : ");
-               label.setHorizontalAlignment(SwingConstants.RIGHT);
-               return label;
-       }
-
-
-       /**
-        * Finish the dialog when OK pressed
-        */
-       private void finish()
-       {
-               if (_widthField.getValue() > 0 && _heightField.getValue() > 0)
-               {
-                       // Set entered values in Config
-                       Config.setConfigInt(Config.KEY_KMZ_IMAGE_WIDTH, _widthField.getValue());
-                       Config.setConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT, _heightField.getValue());
-                       _dialog.dispose();
-               }
-       }
-}
index 44078084ef4af0d7510f476d01f07086bf97f91d..3962787601f9ef789ea39d8ca108781384e41292 100644 (file)
@@ -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",
                "espa\u00F1ol", "fran\u00E7ais", "italiano", "magyar", "nederlands", "polski",
                "portugu\u00EAs", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian)", "\u4e2d\u6587 (chinese)", "\u65E5\u672C\u8A9E (japanese)",
                "\uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean)", "schwiizerd\u00FC\u00FCtsch", "t\u00FCrk\u00E7e",
-               "rom\u00E2n\u0103", "afrikaans", "bahasa indonesia"
+               "afrikaans", "rom\u00E2n\u0103"
        };
        /** Associated language codes (must be in same order as names!) */
        private static final String[] LANGUAGE_CODES = {"cz", "de", "en", "en_us", "es", "fr", "it", "hu",
        };
        /** Associated language codes (must be in same order as names!) */
        private static final String[] LANGUAGE_CODES = {"cz", "de", "en", "en_us", "es", "fr", "it", "hu",
-               "nl", "pl", "pt", "ru", "zh", "ja", "ko", "de_ch", "tr", "ro", "af", "in"
+               "nl", "pl", "pt", "ru", "zh", "ja", "ko", "de_ch", "tr", "af", "ro"
        };
 
 
        };
 
 
index f4ee5617b22cf7eb3a77d7783b93c3901ebeab22..4fb46d7aa2d1443c3af929c1d5ffb05cd44a8ef8 100644 (file)
@@ -10,6 +10,7 @@ import tim.prune.I18nManager;
 public class EditFieldsTableModel extends AbstractTableModel
 {
        private String[] _fieldNames = null;
 public class EditFieldsTableModel extends AbstractTableModel
 {
        private String[] _fieldNames = null;
+       private String[] _originalValues = null;
        private String[] _fieldValues = null;
        private boolean[] _valueChanged = null;
 
        private String[] _fieldValues = null;
        private boolean[] _valueChanged = null;
 
@@ -20,9 +21,10 @@ public class EditFieldsTableModel extends AbstractTableModel
         */
        public EditFieldsTableModel(int inSize)
        {
         */
        public EditFieldsTableModel(int inSize)
        {
-               _fieldNames = new String[inSize];
-               _fieldValues = new String[inSize];
-               _valueChanged = new boolean[inSize];
+               _fieldNames     = new String[inSize];
+               _originalValues = new String[inSize];
+               _fieldValues    = new String[inSize];
+               _valueChanged   = new boolean[inSize];
        }
 
 
        }
 
 
@@ -35,6 +37,7 @@ public class EditFieldsTableModel extends AbstractTableModel
        public void addFieldInfo(String inName, String inValue, int inIndex)
        {
                _fieldNames[inIndex] = inName;
        public void addFieldInfo(String inName, String inValue, int inIndex)
        {
                _fieldNames[inIndex] = inName;
+               _originalValues[inIndex] = inValue;
                _fieldValues[inIndex] = inValue;
                _valueChanged[inIndex] = false;
        }
                _fieldValues[inIndex] = inValue;
                _valueChanged[inIndex] = false;
        }
@@ -45,7 +48,7 @@ public class EditFieldsTableModel extends AbstractTableModel
         */
        public int getColumnCount()
        {
         */
        public int getColumnCount()
        {
-               return 3;
+               return 2;
        }
 
 
        }
 
 
@@ -67,11 +70,7 @@ public class EditFieldsTableModel extends AbstractTableModel
                {
                        return _fieldNames[inRowIndex];
                }
                {
                        return _fieldNames[inRowIndex];
                }
-               else if (inColumnIndex == 1)
-               {
-                       return _fieldValues[inRowIndex];
-               }
-               return Boolean.valueOf(_valueChanged[inRowIndex]);
+               return _fieldValues[inRowIndex];
        }
 
 
        }
 
 
@@ -111,8 +110,7 @@ public class EditFieldsTableModel extends AbstractTableModel
        public String getColumnName(int inColNum)
        {
                if (inColNum == 0) return I18nManager.getText("dialog.pointedit.table.field");
        public String getColumnName(int inColNum)
        {
                if (inColNum == 0) return I18nManager.getText("dialog.pointedit.table.field");
-               else if (inColNum == 1) return I18nManager.getText("dialog.pointedit.table.value");
-               return I18nManager.getText("dialog.pointedit.table.changed");
+               return I18nManager.getText("dialog.pointedit.table.value");
        }
 
 
        }
 
 
@@ -120,28 +118,36 @@ public class EditFieldsTableModel extends AbstractTableModel
         * Update the value of the given row
         * @param inRowNum number of row, starting at 0
         * @param inValue new value
         * Update the value of the given row
         * @param inRowNum number of row, starting at 0
         * @param inValue new value
-        * @return true if data updated
         */
         */
-       public boolean updateValue(int inRowNum, String inValue)
+       public void updateValue(int inRowNum, String inValue)
        {
        {
+               String origValue = _originalValues[inRowNum];
                String currValue = _fieldValues[inRowNum];
                String currValue = _fieldValues[inRowNum];
-               // ignore empty-to-empty changes
-               if ((currValue == null || currValue.equals("")) && (inValue == null || inValue.equals("")))
+               // Update model if changed from original value
+               _valueChanged[inRowNum] = areStringsDifferent(origValue, inValue);
+               // Update model if changed from current value
+               if (areStringsDifferent(currValue, inValue))
                {
                {
-                       return false;
-               }
-               // ignore changes when strings equal
-               if (currValue == null || inValue == null || !currValue.equals(inValue))
-               {
-                       // really changed
                        _fieldValues[inRowNum] = inValue;
                        _fieldValues[inRowNum] = inValue;
-                       _valueChanged[inRowNum] = true;
                        fireTableRowsUpdated(inRowNum, inRowNum);
                        fireTableRowsUpdated(inRowNum, inRowNum);
-                       return true;
                }
                }
-               return false;
        }
 
        }
 
+       /**
+        * Compare two strings to see if they're equal or not (nulls treated the same as empty strings)
+        * @param inString1 first string
+        * @param inString2 second string
+        * @return true if the strings are different
+        */
+       private static boolean areStringsDifferent(String inString1, String inString2)
+       {
+               // if both empty then same
+               if ((inString1 == null || inString1.equals("")) && (inString2 == null || inString2.equals("")))
+               {
+                       return false;
+               }
+               return (inString1 == null || inString2 == null || !inString1.equals(inString2));
+       }
 
        /**
         * Get the value at the given index
 
        /**
         * Get the value at the given index
index 2321ece0bda861184eaecca8879eb134624a87ec..40cf12ba251495dfebca5a673c4f67cf6b3c816e 100644 (file)
@@ -1,31 +1,39 @@
 package tim.prune.function.edit;
 
 import java.awt.BorderLayout;
 package tim.prune.function.edit;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
+import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
 import javax.swing.BorderFactory;
 import 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.JButton;
 import javax.swing.JDialog;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
 import javax.swing.ListSelectionModel;
 import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableCellRenderer;
 
 import tim.prune.App;
 import tim.prune.I18nManager;
 
 import tim.prune.App;
 import tim.prune.I18nManager;
+import tim.prune.config.Config;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.FieldList;
 import tim.prune.data.Track;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.FieldList;
 import tim.prune.data.Track;
+import tim.prune.data.Unit;
 
 /**
  * Class to manage the display and editing of point data
 
 /**
  * Class to manage the display and editing of point data
@@ -36,11 +44,14 @@ public class PointEditor
        private JFrame _parentFrame = null;
        private JDialog _dialog = null;
        private JTable _table = null;
        private JFrame _parentFrame = null;
        private JDialog _dialog = null;
        private JTable _table = null;
+       private JLabel _fieldnameLabel = null;
+       private JTextField _valueField = null;
+       private JTextArea _valueArea = null;
+       private JScrollPane _valueAreaPane = null;
        private Track _track = null;
        private DataPoint _point = null;
        private EditFieldsTableModel _model = null;
        private Track _track = null;
        private DataPoint _point = null;
        private EditFieldsTableModel _model = null;
-       private JButton _editButton = null;
-       private JButton _okButton = null;
+       private int _prevRowIndex = -1;
 
 
        /**
 
 
        /**
@@ -76,9 +87,16 @@ public class PointEditor
                        Field field = fieldList.getField(i);
                        _model.addFieldInfo(field.getName(), _point.getFieldValue(field), i);
                }
                        Field field = fieldList.getField(i);
                        _model.addFieldInfo(field.getName(), _point.getFieldValue(field), i);
                }
-               // Create Gui and show it
+               // Create Gui
                _dialog.getContentPane().add(makeDialogComponents());
                _dialog.pack();
                _dialog.getContentPane().add(makeDialogComponents());
                _dialog.pack();
+               // Init right-hand side
+               SwingUtilities.invokeLater(new Runnable() {
+                       public void run() {
+                               _valueField.setVisible(false);
+                               _valueAreaPane.setVisible(false);
+                       }
+               });
                _dialog.setVisible(true);
        }
 
                _dialog.setVisible(true);
        }
 
@@ -90,44 +108,71 @@ public class PointEditor
        private Component makeDialogComponents()
        {
                JPanel panel = new JPanel();
        private Component makeDialogComponents()
        {
                JPanel panel = new JPanel();
-               panel.setLayout(new BorderLayout(1, 10));
+               panel.setLayout(new BorderLayout(20, 10));
                // Create GUI layout for point editor
                // Create GUI layout for point editor
-               _table = new JTable(_model);
+               _table = new JTable(_model)
+               {
+                       // Paint the changed fields orange
+                       public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
+                       {
+                               Component comp = super.prepareRenderer(renderer, row, column);
+                               boolean changed = ((EditFieldsTableModel) getModel()).getChanged(row);
+                               comp.setBackground(changed ? Color.orange : getBackground());
+                               return comp;
+                       }
+               };
                _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               _table.getSelectionModel().clearSelection();
                _table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
                        public void valueChanged(ListSelectionEvent e)
                        {
                _table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
                        public void valueChanged(ListSelectionEvent e)
                        {
-                               // enable edit button when row selected
-                               _editButton.setEnabled(true);
+                               fieldSelected();
                        }
                });
                        }
                });
-               _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth(), _table.getRowHeight() * 6));
-               panel.add(new JScrollPane(_table), BorderLayout.CENTER);
+               _table.setPreferredScrollableViewportSize(new Dimension(_table.getWidth() * 2, _table.getRowHeight() * 6));
+               JScrollPane tablePane = new JScrollPane(_table);
+               tablePane.setPreferredSize(new Dimension(150, 100));
+
                // Label at top
                // Label at top
-               JLabel topLabel = new JLabel(I18nManager.getText("dialog.pointedit.text"));
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.pointedit.intro"));
                topLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 3, 6));
                panel.add(topLabel, BorderLayout.NORTH);
                topLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 3, 6));
                panel.add(topLabel, BorderLayout.NORTH);
-               _editButton = new JButton(I18nManager.getText("button.edit"));
-               _editButton.addActionListener(new ActionListener() {
+
+               // listener for ok event
+               ActionListener okListener = new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                        public void actionPerformed(ActionEvent e)
                        {
-                               // Update field value and enable ok button
-                               String currValue = _model.getValue(_table.getSelectedRow());
-                               Object newValue = JOptionPane.showInputDialog(_dialog,
-                                       I18nManager.getText("dialog.pointedit.changevalue.text"),
-                                       I18nManager.getText("dialog.pointedit.changevalue.title"),
-                                       JOptionPane.QUESTION_MESSAGE, null, null, currValue);
-                               if (newValue != null
-                                       && _model.updateValue(_table.getSelectedRow(), newValue.toString()))
-                               {
-                                       _okButton.setEnabled(true);
-                               }
+                               // update App with edit
+                               confirmEdit();
+                               _dialog.dispose();
                        }
                        }
-               });
-               _editButton.setEnabled(false);
+               };
+
                JPanel rightPanel = new JPanel();
                JPanel rightPanel = new JPanel();
-               rightPanel.add(_editButton);
-               panel.add(rightPanel, BorderLayout.EAST);
+               rightPanel.setLayout(new BorderLayout());
+               JPanel rightiPanel = new JPanel();
+               rightiPanel.setLayout(new BoxLayout(rightiPanel, BoxLayout.Y_AXIS));
+               // Add GUI elements to rhs
+               _fieldnameLabel = new JLabel(I18nManager.getText("dialog.pointedit.nofield"));
+               rightiPanel.add(_fieldnameLabel);
+               _valueField = new JTextField(11);
+               // Add listener for enter button
+               _valueField.addActionListener(okListener);
+               rightiPanel.add(_valueField);
+               rightPanel.add(rightiPanel, BorderLayout.NORTH);
+               _valueArea = new JTextArea(5, 15);
+               _valueArea.setLineWrap(true);
+               _valueArea.setWrapStyleWord(true);
+               _valueAreaPane = new JScrollPane(_valueArea);
+               rightPanel.add(_valueAreaPane, BorderLayout.CENTER);
+
+               // Put the table and the right-hand panel together in a grid
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new GridLayout(0, 2, 10, 10));
+               mainPanel.add(tablePane);
+               mainPanel.add(rightPanel);
+               panel.add(mainPanel, BorderLayout.CENTER);
+
                // Bottom panel for OK, cancel buttons
                JPanel lowerPanel = new JPanel();
                lowerPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
                // Bottom panel for OK, cancel buttons
                JPanel lowerPanel = new JPanel();
                lowerPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
@@ -139,27 +184,109 @@ public class PointEditor
                        }
                });
                lowerPanel.add(cancelButton);
                        }
                });
                lowerPanel.add(cancelButton);
-               _okButton = new JButton(I18nManager.getText("button.ok"));
-               _okButton.setEnabled(false);
-               _okButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               // update App with edit
-                               confirmEdit();
-                               _dialog.dispose();
-                       }
-               });
-               lowerPanel.add(_okButton);
+               JButton okButton = new JButton(I18nManager.getText("button.ok"));
+               okButton.addActionListener(okListener);
+               lowerPanel.add(okButton);
                panel.add(lowerPanel, BorderLayout.SOUTH);
                return panel;
        }
 
 
                panel.add(lowerPanel, BorderLayout.SOUTH);
                return panel;
        }
 
 
+       /**
+        * When table selection changes, need to update model and go to the selected field
+        */
+       private void fieldSelected()
+       {
+               int rowNum = _table.getSelectedRow();
+               if (rowNum == _prevRowIndex) {return;} // selection hasn't changed
+               // Check the current values
+               if (_prevRowIndex >= 0)
+               {
+                       Field prevField = _track.getFieldList().getField(_prevRowIndex);
+                       boolean littleField = prevField.isBuiltIn() && prevField != Field.DESCRIPTION;
+                       String newValue = littleField ? _valueField.getText() : _valueArea.getText();
+                       // Update the model from the current GUI values
+                       _model.updateValue(_prevRowIndex, newValue);
+               }
+
+               if (rowNum < 0)
+               {
+                       _fieldnameLabel.setText("");
+               }
+               else
+               {
+                       String currValue = _model.getValue(rowNum);
+                       Field  field     = _track.getFieldList().getField(rowNum);
+                       _fieldnameLabel.setText(makeFieldLabel(field, _point));
+                       _fieldnameLabel.setVisible(true);
+                       boolean littleField = field.isBuiltIn() && field != Field.DESCRIPTION;
+                       if (littleField) {
+                               _valueField.setText(currValue);
+                       }
+                       else {
+                               _valueArea.setText(currValue);
+                       }
+                       _valueField.setVisible(littleField);
+                       _valueAreaPane.setVisible(!littleField);
+                       if (littleField) {
+                               _valueField.requestFocus();
+                       }
+                       else {
+                               _valueArea.requestFocus();
+                       }
+               }
+               _prevRowIndex = rowNum;
+       }
+
+       /**
+        * @param inField field
+        * @param inPoint current point
+        * @return label string for above the entry field / area
+        */
+       private static String makeFieldLabel(Field inField, DataPoint inPoint)
+       {
+               String label = I18nManager.getText("dialog.pointedit.table.field") + ": " + inField.getName();
+               // Add units if the field is altitude / speed / vspeed
+               if (inField == Field.ALTITUDE)
+               {
+                       label += makeUnitsLabel(inPoint.hasAltitude() ? inPoint.getAltitude().getUnit() : Config.getUnitSet().getAltitudeUnit());
+               }
+               else if (inField == Field.SPEED)
+               {
+                       label += makeUnitsLabel(inPoint.hasHSpeed() ? inPoint.getHSpeed().getUnit() : Config.getUnitSet().getSpeedUnit());
+               }
+               else if (inField == Field.VERTICAL_SPEED)
+               {
+                       label += makeUnitsLabel(inPoint.hasVSpeed() ? inPoint.getVSpeed().getUnit() : Config.getUnitSet().getVerticalSpeedUnit());
+               }
+               return label;
+       }
+
+       /**
+        * @param inUnit units for altitude / speed
+        * @return addition to the field label to describe the units
+        */
+       private static String makeUnitsLabel(Unit inUnit)
+       {
+               if (inUnit == null) return "";
+               return " (" + I18nManager.getText(inUnit.getShortnameKey()) + ")";
+       }
+
        /**
         * Confirm the edit and inform the app
         */
        private void confirmEdit()
        {
        /**
         * Confirm the edit and inform the app
         */
        private void confirmEdit()
        {
+               // Apply the edits to the current field
+               int rowNum = _table.getSelectedRow();
+               if (rowNum >= 0)
+               {
+                       Field currField = _track.getFieldList().getField(rowNum);
+                       boolean littleField = currField.isBuiltIn() && currField != Field.DESCRIPTION;
+                       String newValue = littleField ? _valueField.getText() : _valueArea.getText();
+                       _model.updateValue(_prevRowIndex, newValue);
+               }
+
                // Package the modified fields into an object
                FieldList fieldList = _track.getFieldList();
                int numFields = fieldList.getNumFields();
                // Package the modified fields into an object
                FieldList fieldList = _track.getFieldList();
                int numFields = fieldList.getNumFields();
index bfa46c4bf4d54f9df5f4e9101b859903a26cd172..53e1f834b962fa8a098a9fc9345934dbdac3003f 100644 (file)
@@ -8,6 +8,7 @@ import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 
+import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
 import javax.swing.JDialog;
@@ -77,7 +78,8 @@ public class PointNameEditor extends GenericFunction
                panel.setLayout(new BorderLayout());
                // Create GUI layout for point name editor
                JPanel centrePanel = new JPanel();
                panel.setLayout(new BorderLayout());
                // Create GUI layout for point name editor
                JPanel centrePanel = new JPanel();
-               centrePanel.add(new JLabel(I18nManager.getText("dialog.pointnameedit.name") + ":"));
+               centrePanel.setLayout(new BorderLayout(8, 8));
+               centrePanel.add(new JLabel(I18nManager.getText("dialog.pointnameedit.name") + ": "), BorderLayout.WEST);
                // Make listener to react to ok being pressed
                ActionListener okActionListener = new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                // Make listener to react to ok being pressed
                ActionListener okActionListener = new ActionListener() {
                        public void actionPerformed(ActionEvent e)
@@ -101,8 +103,13 @@ public class PointNameEditor extends GenericFunction
                        }
                });
                _nameField.addActionListener(okActionListener);
                        }
                });
                _nameField.addActionListener(okActionListener);
-               centrePanel.add(_nameField);
-               panel.add(centrePanel);
+               centrePanel.add(_nameField, BorderLayout.CENTER);
+               // holder panel to stop the text box from being stretched
+               JPanel holderPanel = new JPanel();
+               holderPanel.setLayout(new BorderLayout());
+               holderPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+               holderPanel.add(centrePanel, BorderLayout.NORTH);
+               panel.add(holderPanel, BorderLayout.CENTER);
                JPanel rightPanel = new JPanel();
                rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
                JButton upperButton = new JButton(I18nManager.getText("dialog.pointnameedit.uppercase"));
                JPanel rightPanel = new JPanel();
                rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
                JButton upperButton = new JButton(I18nManager.getText("dialog.pointnameedit.uppercase"));
diff --git a/tim/prune/function/estimate/EstimateTime.java b/tim/prune/function/estimate/EstimateTime.java
new file mode 100644 (file)
index 0000000..7a502e4
--- /dev/null
@@ -0,0 +1,363 @@
+package tim.prune.function.estimate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Selection;
+import tim.prune.data.Unit;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.DisplayUtils;
+import tim.prune.gui.GuiGridLayout;
+
+/**
+ * Class to calculate and show the results of estimating (hike) time for the current range
+ */
+public class EstimateTime extends GenericFunction
+{
+       /** Dialog */
+       private JDialog _dialog = null;
+       /** Labels for distances */
+       private JLabel _distanceLabel = null;
+       /** Labels for durations */
+       private JLabel _estimatedDurationLabel = null, _actualDurationLabel = null;
+       /** Labels for climbs */
+       private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
+       /** Labels for descents */
+       private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
+       /** Labels and text fields for parameters */
+       private JLabel _flatSpeedLabel = null;
+       private DecimalNumberField _flatSpeedField = null;
+       private JLabel _climbParamLabel = null;
+       private DecimalNumberField _gentleClimbField = null, _steepClimbField = null;
+       private JLabel _descentParamLabel = null;
+       private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
+       /** Range stats */
+       private RangeStats _stats = null;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public EstimateTime(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.estimatetime";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Get the stats on the selection before launching the dialog
+               Selection selection = _app.getTrackInfo().getSelection();
+               _stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+
+               if (_stats.getMovingDistance() < 0.01)
+               {
+                       _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.nodistance");
+                       return;
+               }
+               if (_dialog == null)
+               {
+                       // TODO: Check whether params are at default, show tip message if unaltered?
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               updateDetails();
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(5, 5));
+
+               // main panel with a box layout
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+               // Label at top
+               JLabel introLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
+               introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+               introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(introLabel);
+               mainPanel.add(Box.createVerticalStrut(4));
+
+               // Details panel in a grid
+               JPanel detailsPanel = new JPanel();
+               detailsPanel.setLayout(new GridLayout(0, 4, 6, 2));
+               detailsPanel.setBorder(BorderFactory.createTitledBorder(
+                       I18nManager.getText("dialog.estimatetime.details")));
+
+               // Distance
+               JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
+               distLabel.setHorizontalAlignment(JLabel.RIGHT);
+               detailsPanel.add(distLabel);
+               _distanceLabel = new JLabel("5 km");
+               detailsPanel.add(_distanceLabel);
+               detailsPanel.add(new JLabel("")); detailsPanel.add(new JLabel("")); // two blank cells
+
+               detailsPanel.add(new JLabel(""));
+               detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+               detailsPanel.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+               detailsPanel.add(new JLabel("")); // blank cells
+
+               // Climb
+               JLabel climbLabel = new JLabel(I18nManager.getText("dialog.estimatetime.climb") + ": ");
+               climbLabel.setHorizontalAlignment(JLabel.RIGHT);
+               detailsPanel.add(climbLabel);
+               _gentleClimbLabel = new JLabel("1500 m");
+               detailsPanel.add(_gentleClimbLabel);
+               _steepClimbLabel = new JLabel("1500 m");
+               detailsPanel.add(_steepClimbLabel);
+               detailsPanel.add(new JLabel(""));
+
+               // Descent
+               JLabel descentLabel = new JLabel(I18nManager.getText("dialog.estimatetime.descent") + ": ");
+               descentLabel.setHorizontalAlignment(JLabel.RIGHT);
+               detailsPanel.add(descentLabel);
+               _gentleDescentLabel = new JLabel("1500 m");
+               detailsPanel.add(_gentleDescentLabel);
+               _steepDescentLabel = new JLabel("1500 m");
+               detailsPanel.add(_steepDescentLabel);
+               detailsPanel.add(new JLabel(""));
+
+               detailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(detailsPanel);
+               mainPanel.add(Box.createVerticalStrut(4));
+
+               // Parameters panel in a flexible grid
+               JPanel paramsPanel = new JPanel();
+               GuiGridLayout paramsGrid = new GuiGridLayout(paramsPanel, new double[] {1.5, 0.2, 1.0, 0.2, 0.5},
+                       new boolean[] {true, false, false, false, false});
+               paramsPanel.setBorder(BorderFactory.createTitledBorder(
+                       I18nManager.getText("dialog.estimatetime.parameters")));
+               KeyAdapter paramChangeListener = new KeyAdapter() {
+                       public void keyTyped(KeyEvent inE) {
+                               SwingUtilities.invokeLater(new Runnable() {
+                                       public void run() {
+                                               calculateEstimatedTime();
+                                       }
+                               });
+                       }
+                       public void keyPressed(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+               };
+
+               // Flat speed
+               _flatSpeedLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+               _flatSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
+               paramsGrid.add(_flatSpeedLabel);
+               _flatSpeedField = new DecimalNumberField();
+               _flatSpeedField.addKeyListener(paramChangeListener);
+               paramsGrid.add(_flatSpeedField);
+               JLabel minsLabel = new JLabel(I18nManager.getText("units.minutes"));
+               paramsGrid.add(minsLabel);
+               paramsGrid.nextRow();
+               // Headers for gentle and steep
+               paramsGrid.add(new JLabel(""));
+               paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.gentle")));
+               paramsGrid.add(new JLabel("")); // blank cell
+               paramsGrid.add(new JLabel(I18nManager.getText("dialog.estimatetime.steep")));
+               paramsGrid.nextRow();
+               // Gentle climb
+               _climbParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+               _climbParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+               paramsGrid.add(_climbParamLabel);
+               _gentleClimbField = new DecimalNumberField();
+               _gentleClimbField.addKeyListener(paramChangeListener);
+               paramsGrid.add(_gentleClimbField);
+               paramsGrid.add(new JLabel(minsLabel.getText()));
+               // Steep climb
+               _steepClimbField = new DecimalNumberField();
+               _steepClimbField.addKeyListener(paramChangeListener);
+               paramsGrid.add(_steepClimbField);
+               paramsGrid.add(new JLabel(minsLabel.getText()));
+
+               // Gentle descent
+               _descentParamLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": "); // (filled in later)
+               _descentParamLabel.setHorizontalAlignment(JLabel.RIGHT);
+               paramsGrid.add(_descentParamLabel);
+               _gentleDescentField = new DecimalNumberField(true); // negative numbers allowed
+               _gentleDescentField.addKeyListener(paramChangeListener);
+               paramsGrid.add(_gentleDescentField);
+               paramsGrid.add(new JLabel(minsLabel.getText()));
+               // Steep climb
+               _steepDescentField = new DecimalNumberField(true); // negative numbers allowed
+               _steepDescentField.addKeyListener(paramChangeListener);
+               paramsGrid.add(_steepDescentField);
+               paramsGrid.add(new JLabel(minsLabel.getText()));
+
+               paramsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(paramsPanel);
+               mainPanel.add(Box.createVerticalStrut(12));
+
+               // Results panel
+               JPanel resultsPanel = new JPanel();
+               resultsPanel.setBorder(BorderFactory.createTitledBorder(
+                       I18nManager.getText("dialog.estimatetime.results")));
+               resultsPanel.setLayout(new GridLayout(0, 2, 3, 3));
+               // estimated time
+               _estimatedDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "); // filled in later
+               Font origFont = _estimatedDurationLabel.getFont();
+               _estimatedDurationLabel.setFont(origFont.deriveFont(Font.BOLD, origFont.getSize2D() + 2.0f));
+
+               resultsPanel.add(_estimatedDurationLabel);
+               // actual time (if available)
+               _actualDurationLabel = new JLabel(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "); // filled in later
+               resultsPanel.add(_actualDurationLabel);
+
+               resultsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(resultsPanel);
+               mainPanel.add(Box.createVerticalStrut(4));
+
+               dialogPanel.add(mainPanel, BorderLayout.NORTH);
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton closeButton = new JButton(I18nManager.getText("button.close"));
+               closeButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               finishDialog();
+                       }
+               });
+               closeButton.addKeyListener(new KeyAdapter() {
+                       public void keyPressed(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+               });
+               buttonPanel.add(closeButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+
+       /**
+        * Recalculate the values and update the labels
+        */
+       private void updateDetails()
+       {
+               // Warn if the current track hasn't got any height information
+               if (!_stats.getMovingAltitudeRange().hasRange()) {
+                       _app.showErrorMessage(getNameKey(), "dialog.estimatetime.error.noaltitudes");
+               }
+
+               // Distance in current units
+               final Unit distUnit = Config.getUnitSet().getDistanceUnit();
+               final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
+               final double movingDist = _stats.getMovingDistance();
+               _distanceLabel.setText(DisplayUtils.roundedNumber(movingDist) + " " + distUnitsStr);
+
+               // Climb and descent values
+               final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+               final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
+               _gentleClimbLabel.setText(_stats.getGentleAltitudeRange().getClimb(altUnit) + altUnitsStr);
+               _steepClimbLabel.setText(_stats.getSteepAltitudeRange().getClimb(altUnit) + altUnitsStr);
+               _gentleDescentLabel.setText(_stats.getGentleAltitudeRange().getDescent(altUnit) + altUnitsStr);
+               _steepDescentLabel.setText(_stats.getSteepAltitudeRange().getDescent(altUnit) + altUnitsStr);
+
+               // Try to get parameters from config
+               EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+
+               String[] paramValues = estParams.getStrings();
+               if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
+               // Flat time is either for 5 km, 3 miles or 3 nm
+               _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
+                       " " + EstimationParameters.getStandardDistance() + ": ");
+               _flatSpeedField.setText(paramValues[0]);
+
+               final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
+               _climbParamLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
+               _gentleClimbField.setText(paramValues[1]);
+               _steepClimbField.setText(paramValues[2]);
+               _descentParamLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
+               _gentleDescentField.setText(paramValues[3]);
+               _steepDescentField.setText(paramValues[4]);
+
+               // Use the entered parameters to estimate the time
+               calculateEstimatedTime();
+
+               // Get the actual time if available, for comparison
+               if (_stats.getMovingDurationInSeconds() > 0)
+               {
+                       _actualDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.actualtime") + ": "
+                               + DisplayUtils.buildDurationString(_stats.getMovingDurationInSeconds()));
+               }
+               else {
+                       _actualDurationLabel.setText("");
+               }
+       }
+
+
+       /**
+        * Use the current parameter and the range stats to calculate the estimated time
+        * and populate the answer in the dialog
+        */
+       private void calculateEstimatedTime()
+       {
+               // Populate an EstimationParameters object from the four strings
+               EstimationParameters params = new EstimationParameters();
+               params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+                       _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+               if (!params.isValid()) {
+                       _estimatedDurationLabel.setText("- - -");
+               }
+               else
+               {
+                       final long numSeconds = (long) (params.applyToStats(_stats) * 60.0);
+                       _estimatedDurationLabel.setText(I18nManager.getText("dialog.estimatetime.results.estimatedtime") + ": "
+                               + DisplayUtils.buildDurationString(numSeconds));
+               }
+       }
+
+
+       /**
+        * Finish with the dialog, by pressing the "Close" button
+        */
+       private void finishDialog()
+       {
+               // Make estimation parameters from entered strings, if valid save to config
+               EstimationParameters params = new EstimationParameters();
+               params.populateWithStrings(_flatSpeedField.getText(), _gentleClimbField.getText(),
+                       _steepClimbField.getText(), _gentleDescentField.getText(), _steepDescentField.getText());
+               if (params.isValid()) {
+                       Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
+               }
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/function/estimate/EstimationParameters.java b/tim/prune/function/estimate/EstimationParameters.java
new file mode 100644 (file)
index 0000000..e78994b
--- /dev/null
@@ -0,0 +1,278 @@
+package tim.prune.function.estimate;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+
+/**
+ * Class to hold, parse and convert the parameters for time estimation.
+ * These are five (metric) values which can be loaded and saved from config,
+ * and are used by the EstimateTime function
+ */
+public class EstimationParameters
+{
+       /** Minutes required for flat travel, fixed metric distance */
+       private double _flatMins = 0.0;
+       /** Minutes required for climbing, fixed metric distance */
+       private double _gentleClimbMins = 0.0, _steepClimbMins;
+       /** Minutes required for descending, fixed metric distance */
+       private double _gentleDescentMins = 0.0, _steepDescentMins;
+       /** True if parsing from a string failed */
+       private boolean _parseFailed = false;
+
+       /** Kilometres unit for comparison */
+       private static final Unit KILOMETRES = UnitSetLibrary.UNITS_KILOMETRES;
+
+
+       /**
+        * Bare constructor using default values
+        */
+       public EstimationParameters()
+       {
+               resetToDefaults();
+       }
+
+       /**
+        * Constructor from config string
+        * @param inConfigString single, semicolon-separated string from config
+        */
+       public EstimationParameters(String inConfigString)
+       {
+               populateWithString(inConfigString);
+               if (_parseFailed) {
+                       resetToDefaults();
+               }
+       }
+
+       /**
+        * Reset all the values to their hardcoded defaults
+        */
+       public void resetToDefaults()
+       {
+               _flatMins = 60.0;
+               _gentleClimbMins = 12.0; _steepClimbMins = 18.0;
+               _gentleDescentMins = 0.0; _steepDescentMins = 12.0;
+               _parseFailed = false;
+       }
+
+       /**
+        * Populate the values from the config, which means all values are metric
+        * @param inString semicolon-separated string of five parameters
+        */
+       private void populateWithString(String inString)
+       {
+               if (inString != null && !inString.trim().equals(""))
+               {
+                       String[] params = inString.trim().split(";");
+                       _parseFailed = (params == null || params.length != 5);
+                       if (!_parseFailed)
+                       {
+                               for (String p : params)
+                               {
+                                       if (!isParamStringValid(p)) {
+                                               _parseFailed = true;
+                                       }
+                               }
+                       }
+                       if (!_parseFailed)
+                       {
+                               try
+                               {
+                                       // Use fixed UK locale to parse these, because of fixed "." formatting
+                                       NumberFormat twoDpFormatter = NumberFormat.getNumberInstance(Locale.UK);
+                                       _flatMins          = twoDpFormatter.parse(params[0]).doubleValue();
+                                       _gentleClimbMins   = twoDpFormatter.parse(params[1]).doubleValue();
+                                       _steepClimbMins    = twoDpFormatter.parse(params[2]).doubleValue();
+                                       _gentleDescentMins = twoDpFormatter.parse(params[3]).doubleValue();
+                                       _steepDescentMins  = twoDpFormatter.parse(params[4]).doubleValue();
+                               }
+                               catch (Exception e) {
+                                       _parseFailed = true;
+                               }
+                       }
+               }
+               else _parseFailed = true;
+       }
+
+       /**
+        * Populate the values using five user-entered strings (now Units-specific!)
+        * @param inFlat minutes for flat
+        * @param inGClimb minutes for gentle climb
+        * @param inSClimb minutes for steep climb
+        * @param inGDescent minutes for gentle descent
+        * @param inSDescent minutes for steep descent
+        */
+       public void populateWithStrings(String inFlat, String inGClimb, String inSClimb, String inGDescent, String inSDescent)
+       {
+               if (isParamStringValid(inFlat) && isParamStringValid(inGClimb) && isParamStringValid(inSClimb)
+                       && isParamStringValid(inGDescent) && isParamStringValid(inSDescent))
+               {
+                       Unit distUnit = Config.getUnitSet().getDistanceUnit();
+                       Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
+                       final double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
+                       final double altFactor  = (altUnit.isStandard()  ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+                       NumberFormat localFormatter = NumberFormat.getNumberInstance();
+                       try
+                       {
+                               _flatMins = localFormatter.parse(inFlat).doubleValue() * distFactor;
+                               _gentleClimbMins = localFormatter.parse(inGClimb).doubleValue() * altFactor;
+                               _steepClimbMins  = localFormatter.parse(inSClimb).doubleValue() * altFactor;
+                               _gentleDescentMins = localFormatter.parse(inGDescent).doubleValue() * altFactor;
+                               _steepDescentMins  = localFormatter.parse(inSDescent).doubleValue() * altFactor;
+                       }
+                       catch (Exception e) {_parseFailed = true;}
+               }
+               else _parseFailed = true;
+       }
+
+       /**
+        * Populate with double metric values, for example the results of a Learning process
+        * @param inFlat time for 5km on the flat
+        * @param inGClimb time for 100m gentle climb
+        * @param inSClimb time for 100m steep climb
+        * @param inGDescent time for 100m gentle descent
+        * @param inSDescent time for 100m steep descent
+        */
+       public void populateWithMetrics(double inFlat, double inGClimb, double inSClimb, double inGDescent, double inSDescent)
+       {
+               _flatMins = inFlat;
+               _gentleClimbMins = inGClimb;
+               _steepClimbMins  = inSClimb;
+               _gentleDescentMins = inGDescent;
+               _steepDescentMins  = inSDescent;
+       }
+
+       /**
+        * @param inString parameter string to check
+        * @return true if it looks valid (no letters, at least one digit)
+        */
+       private static boolean isParamStringValid(String inString)
+       {
+               if (inString == null) {return false;}
+               boolean hasDigit = false, currPunc = false, prevPunc = false;
+               for (int i=0; i<inString.length(); i++)
+               {
+                       char c = inString.charAt(i);
+                       if (Character.isLetter(c)) {return false;} // no letters allowed
+                       currPunc = (c == '.' || c == ',');
+                       if (currPunc && prevPunc) {return false;} // no consecutive . or , allowed
+                       prevPunc = currPunc;
+                       hasDigit = hasDigit || Character.isDigit(c);
+               }
+               return hasDigit; // must have at least one digit!
+       }
+
+       /**
+        * @return true if the parameters are valid, with no parsing errors
+        */
+       public boolean isValid()
+       {
+               return !_parseFailed; // && _flatMins > 0.0 && _gentleClimbMins >= 0.0 && _steepClimbMins >= 0.0;
+       }
+
+       /**
+        * @return five strings for putting in text fields for editing / display
+        */
+       public String[] getStrings()
+       {
+               Unit distUnit = Config.getUnitSet().getDistanceUnit();
+               Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
+               double distFactor = (distUnit == KILOMETRES ? 1.0 : (5000/3.0 * distUnit.getMultFactorFromStd()));
+               double altFactor  = (altUnit.isStandard()  ? 1.0 : (1.0/3.0 * altUnit.getMultFactorFromStd()));
+               // Use locale-specific number formatting, eg commas for german
+               NumberFormat numFormatter = NumberFormat.getNumberInstance();
+               if (numFormatter instanceof DecimalFormat) {
+                       ((DecimalFormat) numFormatter).applyPattern("0.00");
+               }
+               // Conversion between units
+               return new String[] {
+                       numFormatter.format(_flatMins / distFactor),
+                       numFormatter.format(_gentleClimbMins / altFactor), numFormatter.format(_steepClimbMins / altFactor),
+                       numFormatter.format(_gentleDescentMins / altFactor), numFormatter.format(_steepDescentMins / altFactor)
+               };
+       }
+
+       /**
+        * @return unit-specific string describing the distance for the flat time (5km/3mi/3NM)
+        */
+       public static String getStandardDistance()
+       {
+               Unit distUnit = Config.getUnitSet().getDistanceUnit();
+               return (distUnit == KILOMETRES ? "5 " : "3 ") + I18nManager.getText(distUnit.getShortnameKey());
+       }
+
+       /**
+        * @return unit-specific string describing the height difference for the climbs/descents (100m/300ft)
+        */
+       public static String getStandardClimb()
+       {
+               Unit altUnit  = Config.getUnitSet().getAltitudeUnit();
+               return (altUnit.isStandard() ? "100 " : "300 ") + I18nManager.getText(altUnit.getShortnameKey());
+       }
+
+       /**
+        * @return contents of parameters as a semicolon-separated (metric) string for the config
+        */
+       public String toConfigString()
+       {
+               return "" + twoDp(_flatMins) + ";" + twoDp(_gentleClimbMins) + ";" + twoDp(_steepClimbMins) + ";"
+                       + twoDp(_gentleDescentMins) + ";" + twoDp(_steepDescentMins);
+       }
+
+       /**
+        * @param inNum number to output
+        * @return formatted string to two decimal places, with decimal point
+        */
+       private static String twoDp(double inNum)
+       {
+               if (inNum < 0.0) return "-" + twoDp(-inNum);
+               int hundreds = (int) (inNum * 100 + 0.5);
+               return "" + (hundreds / 100) + "." + (hundreds % 100);
+       }
+
+       /**
+        * Apply the parameters to the given range stats
+        * @param inStats stats of current range
+        * @return estimated number of minutes required
+        */
+       public double applyToStats(RangeStats inStats)
+       {
+               if (inStats == null || !inStats.isValid()) return 0.0;
+               final Unit METRES = UnitSetLibrary.UNITS_METRES;
+               final double STANDARD_CLIMB = 100.0; // metres
+               final double STANDARD_DISTANCE = 5.0; // kilometres
+               return _flatMins * inStats.getMovingDistanceKilometres() / STANDARD_DISTANCE
+                       + _gentleClimbMins * inStats.getGentleAltitudeRange().getClimb(METRES) / STANDARD_CLIMB
+                       + _steepClimbMins  * inStats.getSteepAltitudeRange().getClimb(METRES) / STANDARD_CLIMB
+                       + _gentleDescentMins * inStats.getGentleAltitudeRange().getDescent(METRES) / STANDARD_CLIMB
+                       + _steepDescentMins  * inStats.getSteepAltitudeRange().getDescent(METRES) / STANDARD_CLIMB;
+       }
+
+       /**
+        * Combine two sets of parameters together
+        * @param inOther other set
+        * @param inFraction fractional weight
+        * @return combined set
+        */
+       public EstimationParameters combine(EstimationParameters inOther, double inFraction)
+       {
+               if (inFraction < 0.0 || inFraction > 1.0 || inOther == null) {
+                       return null;
+               }
+               // inFraction is the weight of this one, weight of the other one is 1-inFraction
+               final double fraction2 = 1 - inFraction;
+               EstimationParameters combined = new EstimationParameters();
+               combined._flatMins = inFraction * _flatMins + fraction2 * inOther._flatMins;
+               combined._gentleClimbMins = inFraction * _gentleClimbMins + fraction2 * inOther._gentleClimbMins;
+               combined._gentleDescentMins = inFraction * _gentleDescentMins + fraction2 * inOther._gentleDescentMins;
+               combined._steepClimbMins = inFraction * _steepClimbMins + fraction2 * inOther._steepClimbMins;
+               combined._steepDescentMins = inFraction * _steepDescentMins + fraction2 * inOther._steepDescentMins;
+               return combined;
+       }
+}
diff --git a/tim/prune/function/estimate/LearnParameters.java b/tim/prune/function/estimate/LearnParameters.java
new file mode 100644 (file)
index 0000000..8f4ea68
--- /dev/null
@@ -0,0 +1,520 @@
+package tim.prune.function.estimate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Distance;
+import tim.prune.data.RangeStats;
+import tim.prune.data.Track;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.function.estimate.jama.Matrix;
+import tim.prune.gui.ProgressDialog;
+
+/**
+ * Function to learn the estimation parameters from the current track
+ */
+public class LearnParameters extends GenericFunction implements Runnable
+{
+       /** Progress dialog */
+       ProgressDialog _progress = null;
+       /** Results dialog */
+       JDialog _dialog = null;
+       /** Calculated parameters */
+       private ParametersPanel _calculatedParamPanel = null;
+       private EstimationParameters _calculatedParams = null;
+       /** Slider for weighted average */
+       private JScrollBar _weightSlider = null;
+       /** Label to describe position of slider */
+       private JLabel _sliderDescLabel = null;
+       /** Combined parameters */
+       private ParametersPanel _combinedParamPanel = null;
+       /** Combine button */
+       private JButton _combineButton = null;
+
+
+       /**
+        * Inner class used to hold the results of the matrix solving
+        */
+       static class MatrixResults
+       {
+               public EstimationParameters _parameters = null;
+               public double _averageErrorPc = 0.0; // percentage
+       }
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public LearnParameters(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** @return key for function name */
+       public String getNameKey() {
+               return "function.learnestimationparams";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Show progress bar
+               if (_progress == null) {
+                       _progress = new ProgressDialog(_parentFrame, getNameKey());
+               }
+               _progress.show();
+               // Start new thread for the calculations
+               new Thread(this).start();
+       }
+
+       /**
+        * Run method in separate thread
+        */
+       public void run()
+       {
+               _progress.setMaximum(100);
+               // Go through the track and collect the range stats for each sample
+               ArrayList<RangeStats> statsList = new ArrayList<RangeStats>(20);
+               Track track = _app.getTrackInfo().getTrack();
+               final int numPoints = track.getNumPoints();
+               final int sampleSize = numPoints / 30;
+               int prevStartIndex = -1;
+               for (int i=0; i<30; i++)
+               {
+                       int startIndex = i * sampleSize;
+                       RangeStats stats = getRangeStats(track, startIndex, startIndex + sampleSize, prevStartIndex);
+                       if (stats != null && stats.getMovingDistanceKilometres() > 1.0
+                               && !stats.getTimestampsIncomplete()
+                               && stats.getTotalDurationInSeconds() > 100
+                               && stats.getStartIndex() > prevStartIndex)
+                       {
+                               // System.out.println("Got stats for " + stats.getStartIndex() + " to " + stats.getEndIndex());
+                               statsList.add(stats);
+                               prevStartIndex = stats.getStartIndex();
+                       }
+                       _progress.setValue(i);
+               }
+
+               // Check if we've got enough samples
+               // System.out.println("Got a total of " + statsList.size() + " samples");
+               if (statsList.size() < 10)
+               {
+                       _progress.dispose();
+                       // Show error message, not enough samples
+                       _app.showErrorMessage(getNameKey(), "error.learnestimationparams.failed");
+                       return;
+               }
+               // Loop around, solving the matrices and removing the highest-error sample
+               MatrixResults results = reduceSamples(statsList);
+               if (results == null)
+               {
+                       _progress.dispose();
+                       _app.showErrorMessage(getNameKey(), "error.learnestimationparams.failed");
+                       return;
+               }
+
+               _progress.dispose();
+
+               // Create the dialog if necessary
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       // Create Gui and show it
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+
+               // Populate the values in the dialog
+               populateCalculatedValues(results);
+               updateCombinedLabels(calculateCombinedParameters());
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Make the dialog components
+        * @return the GUI components for the dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+
+               // main panel with a box layout
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+               // Label at top
+               JLabel introLabel = new JLabel(I18nManager.getText("dialog.learnestimationparams.intro") + ":");
+               introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+               introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(introLabel);
+
+               // Panel for the calculated results
+               _calculatedParamPanel = new ParametersPanel("dialog.estimatetime.results", true);
+               _calculatedParamPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(_calculatedParamPanel);
+               mainPanel.add(Box.createVerticalStrut(14));
+
+               mainPanel.add(new JLabel(I18nManager.getText("dialog.learnestimationparams.combine") + ":"));
+               mainPanel.add(Box.createVerticalStrut(4));
+               _weightSlider = new JScrollBar(JScrollBar.HORIZONTAL, 5, 1, 0, 11);
+               _weightSlider.addAdjustmentListener(new AdjustmentListener() {
+                       public void adjustmentValueChanged(AdjustmentEvent inEvent)
+                       {
+                               if (!inEvent.getValueIsAdjusting()) {
+                                       updateCombinedLabels(calculateCombinedParameters());
+                               }
+                       }
+               });
+               mainPanel.add(_weightSlider);
+               _sliderDescLabel = new JLabel(" ");
+               _sliderDescLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(_sliderDescLabel);
+               mainPanel.add(Box.createVerticalStrut(12));
+
+               // Results panel
+               _combinedParamPanel = new ParametersPanel("dialog.learnestimationparams.combinedresults");
+               _combinedParamPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               mainPanel.add(_combinedParamPanel);
+
+               dialogPanel.add(mainPanel, BorderLayout.NORTH);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+
+               // Combine
+               _combineButton = new JButton(I18nManager.getText("button.combine"));
+               _combineButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               combineAndFinish();
+                       }
+               });
+               buttonPanel.add(_combineButton);
+
+               // Cancel
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               KeyAdapter escapeListener = new KeyAdapter() {
+                       public void keyPressed(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+               };
+               _combineButton.addKeyListener(escapeListener);
+               cancelButton.addKeyListener(escapeListener);
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+       /**
+        * Construct a rangestats object for the selected range
+        * @param inTrack track object
+        * @param inStartIndex start index
+        * @param inEndIndex end index
+        * @param inPreviousStartIndex the previously used start index, or -1
+        * @return range stats object or null if required information missing from this bit of the track
+        */
+       private RangeStats getRangeStats(Track inTrack, int inStartIndex, int inEndIndex, int inPreviousStartIndex)
+       {
+               // Check parameters
+               if (inTrack == null || inStartIndex < 0 || inEndIndex <= inStartIndex || inStartIndex > inTrack.getNumPoints()) {
+                       return null;
+               }
+               final int numPoints = inTrack.getNumPoints();
+               int start = inStartIndex;
+
+               // Search forward until a decent track point found for the start
+               DataPoint p = inTrack.getPoint(start);
+               while (start < numPoints && (p == null || p.isWaypoint() || !p.hasTimestamp() || !p.hasAltitude()))
+               {
+                       start++;
+                       p = inTrack.getPoint(start);
+               }
+               if (inPreviousStartIndex >= 0 && start <= (inPreviousStartIndex + 10) // overlapping too much with previous range
+                       || (start >= (numPoints - 10))) // starting too late in the track
+               {
+                       return null;
+               }
+
+               // Search forward (counting the radians) until a decent end point found
+               double movingRads = 0.0;
+               final double minimumRads = Distance.convertDistanceToRadians(1.0, UnitSetLibrary.UNITS_KILOMETRES);
+               DataPoint prevPoint = inTrack.getPoint(start);
+               int endIndex = start;
+               boolean shouldStop = false;
+               do
+               {
+                       endIndex++;
+                       p = inTrack.getPoint(endIndex);
+                       if (p != null && !p.isWaypoint())
+                       {
+                               if (!p.hasAltitude() || !p.hasTimestamp()) {return null;} // abort if no time/altitude
+                               if (prevPoint != null && !p.getSegmentStart()) {
+                                       movingRads += DataPoint.calculateRadiansBetween(prevPoint, p);
+                               }
+                       }
+                       prevPoint = p;
+                       if (endIndex >= numPoints) {
+                               shouldStop = true; // reached the end of the track
+                       }
+                       else if (movingRads >= minimumRads && endIndex >= inEndIndex) {
+                               shouldStop = true; // got at least a kilometre
+                       }
+               }
+               while (!shouldStop);
+
+               // Check moving distance
+               if (movingRads >= minimumRads) {
+                       return new RangeStats(inTrack, start, endIndex);
+               }
+               return null;
+       }
+
+       /**
+        * Build an A matrix for the given list of RangeStats objects
+        * @param inStatsList list of (non-null) RangeStats objects
+        * @return A matrix with n rows and 5 columns
+        */
+       private static Matrix buildAMatrix(ArrayList<RangeStats> inStatsList)
+       {
+               final Unit METRES = UnitSetLibrary.UNITS_METRES;
+               Matrix result = new Matrix(inStatsList.size(), 5);
+               int row = 0;
+               for (RangeStats stats : inStatsList)
+               {
+                       result.setValue(row, 0, stats.getMovingDistanceKilometres());
+                       result.setValue(row, 1, stats.getGentleAltitudeRange().getClimb(METRES));
+                       result.setValue(row, 2, stats.getSteepAltitudeRange().getClimb(METRES));
+                       result.setValue(row, 3, stats.getGentleAltitudeRange().getDescent(METRES));
+                       result.setValue(row, 4, stats.getSteepAltitudeRange().getDescent(METRES));
+                       row++;
+               }
+               return result;
+       }
+
+       /**
+        * Build a B matrix containing the observations (moving times)
+        * @param inStatsList list of (non-null) RangeStats objects
+        * @return B matrix with single column of n rows
+        */
+       private static Matrix buildBMatrix(ArrayList<RangeStats> inStatsList)
+       {
+               Matrix result = new Matrix(inStatsList.size(), 1);
+               int row = 0;
+               for (RangeStats stats : inStatsList)
+               {
+                       result.setValue(row, 0, stats.getMovingDurationInSeconds() / 60.0); // convert seconds to minutes
+                       row++;
+               }
+               return result;
+       }
+
+       /**
+        * Look for the maximum absolute value in the given column matrix
+        * @param inMatrix matrix with only one column
+        * @return row index of cell with greatest absolute value, or -1 if not valid
+        */
+       private static int getIndexOfMaxValue(Matrix inMatrix)
+       {
+               if (inMatrix == null || inMatrix.getNumColumns() > 1) {
+                       return -1;
+               }
+               int index = 0;
+               double currValue = 0.0, maxValue = 0.0;
+               // Loop over the first column looking for the maximum absolute value
+               for (int i=0; i<inMatrix.getNumRows(); i++)
+               {
+                       currValue = Math.abs(inMatrix.get(i, 0));
+                       if (currValue > maxValue)
+                       {
+                               maxValue = currValue;
+                               index = i;
+                       }
+               }
+               return index;
+       }
+
+       /**
+        * See if the given set of samples is sufficient for getting a descent solution (at least 3 nonzero values)
+        * @param inRangeSet list of RangeStats objects
+        * @param inRowToIgnore row index to ignore, or -1 to use them all
+        * @return true if the samples look ok
+        */
+       private static boolean isRangeSetSufficient(ArrayList<RangeStats> inRangeSet, int inRowToIgnore)
+       {
+               int numGC = 0, numSC = 0, numGD = 0, numSD = 0; // number of samples with gentle/steep climb/descent values > 0
+               final Unit METRES = UnitSetLibrary.UNITS_METRES;
+               int i = 0;
+               for (RangeStats stats : inRangeSet)
+               {
+                       if (i != inRowToIgnore)
+                       {
+                               if (stats.getGentleAltitudeRange().getClimb(METRES) > 0) {numGC++;}
+                               if (stats.getSteepAltitudeRange().getClimb(METRES) > 0)  {numSC++;}
+                               if (stats.getGentleAltitudeRange().getDescent(METRES) > 0) {numGD++;}
+                               if (stats.getSteepAltitudeRange().getDescent(METRES) > 0)  {numSD++;}
+                       }
+                       i++;
+               }
+               return numGC > 3 && numSC > 3 && numGD > 3 && numSD > 3;
+       }
+
+       /**
+        * Reduce the number of samples in the given list by eliminating the ones with highest errors
+        * @param inStatsList list of stats
+        * @return results in an object
+        */
+       private MatrixResults reduceSamples(ArrayList<RangeStats> inStatsList)
+       {
+               int statsIndexToRemove = -1;
+               Matrix answer = null;
+               boolean finished = false;
+               double averageErrorPc = 0.0;
+               while (!finished)
+               {
+                       // Remove the marked stats object, if any
+                       if (statsIndexToRemove >= 0) {
+                               inStatsList.remove(statsIndexToRemove);
+                       }
+
+                       // Build up the matrices
+                       Matrix A = buildAMatrix(inStatsList);
+                       Matrix B = buildBMatrix(inStatsList);
+                       // System.out.println("Times in minutes are:\n" + B.toString());
+
+                       // Solve (if possible)
+                       try
+                       {
+                               answer = A.solve(B);
+                               // System.out.println("Solved matrix with " + A.getNumRows() + " rows:\n" + answer.toString());
+                               // Work out the percentage error for each estimate
+                               Matrix estimates = A.times(answer);
+                               Matrix errors = estimates.minus(B).divideEach(B);
+                               // System.out.println("Errors: " + errors.toString());
+                               averageErrorPc = errors.getAverageAbsValue();
+                               // find biggest percentage error, remove it from list
+                               statsIndexToRemove = getIndexOfMaxValue(errors);
+                               if (statsIndexToRemove < 0)
+                               {
+                                       System.err.println("Something wrong - index is " + statsIndexToRemove);
+                                       throw new Exception();
+                               }
+                               // Check whether removing this element would make the range set insufficient
+                               finished = inStatsList.size() <= 25 || !isRangeSetSufficient(inStatsList, statsIndexToRemove);
+                       }
+                       catch (Exception e)
+                       {
+                               // Couldn't solve at all
+                               System.out.println("Failed to reduce: " + e.getClass().getName() + " - " + e.getMessage());
+                               return null;
+                       }
+                       _progress.setValue(20 + 80 * (30 - inStatsList.size())/5); // Counting from 30 to 25
+               }
+               // Copy results to an EstimationParameters object
+               MatrixResults result = new MatrixResults();
+               result._parameters = new EstimationParameters();
+               result._parameters.populateWithMetrics(answer.get(0, 0) * 5, // convert from 1km to 5km
+                       answer.get(1, 0) * 100.0, answer.get(2, 0) * 100.0,      // convert from m to 100m
+                       answer.get(3, 0) * 100.0, answer.get(4, 0) * 100.0);
+               result._averageErrorPc = averageErrorPc;
+               return result;
+       }
+
+
+       /**
+        * Populate the dialog's labels with the calculated values
+        * @param inResults results of the calculations
+        */
+       private void populateCalculatedValues(MatrixResults inResults)
+       {
+               if (inResults == null || inResults._parameters == null)
+               {
+                       _calculatedParams = null;
+                       _calculatedParamPanel.updateParameters(null, 0.0);
+               }
+               else
+               {
+                       _calculatedParams = inResults._parameters;
+                       _calculatedParamPanel.updateParameters(_calculatedParams, inResults._averageErrorPc);
+               }
+       }
+
+       /**
+        * Combine the calculated parameters with the existing ones
+        * according to the value of the slider
+        * @return combined parameters
+        */
+       private EstimationParameters calculateCombinedParameters()
+       {
+               final double fraction1 = 1 - 0.1 * _weightSlider.getValue(); // slider left = value 0 = fraction 1 = keep current
+               EstimationParameters oldParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
+               return oldParams.combine(_calculatedParams, fraction1);
+       }
+
+       /**
+        * Update the labels to show the combined parameters
+        * @param inCombinedParams combined estimation parameters
+        */
+       private void updateCombinedLabels(EstimationParameters inCombinedParams)
+       {
+               // Update the slider description label
+               String sliderDesc = null;
+               final int sliderVal = _weightSlider.getValue();
+               switch (sliderVal)
+               {
+                       case 0:  sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.100pccurrent"); break;
+                       case 5:  sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.50pc"); break;
+                       case 10: sliderDesc = I18nManager.getText("dialog.learnestimationparams.weight.100pccalculated"); break;
+                       default:
+                               final int currTenths = 10 - sliderVal, calcTenths = sliderVal;
+                               sliderDesc = "" + currTenths + "0% " + I18nManager.getText("dialog.learnestimationparams.weight.current")
+                                       + " + " + calcTenths + "0% " + I18nManager.getText("dialog.learnestimationparams.weight.calculated");
+               }
+               _sliderDescLabel.setText(sliderDesc);
+               // And update all the combined params labels
+               _combinedParamPanel.updateParameters(inCombinedParams);
+               _combineButton.setEnabled(sliderVal > 0);
+       }
+
+       /**
+        * React to the combine button, by saving the combined parameters in the config
+        */
+       private void combineAndFinish()
+       {
+               EstimationParameters params = calculateCombinedParameters();
+               Config.setConfigString(Config.KEY_ESTIMATION_PARAMS, params.toConfigString());
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/function/estimate/ParametersPanel.java b/tim/prune/function/estimate/ParametersPanel.java
new file mode 100644 (file)
index 0000000..3632831
--- /dev/null
@@ -0,0 +1,158 @@
+package tim.prune.function.estimate;
+
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DisplayUtils;
+
+/**
+ * Display panel for showing estimation parameters
+ * in a standard grid form
+ */
+public class ParametersPanel extends JPanel
+{
+       /** Flag for whether average error should be shown */
+       private boolean _showAverageError = false;
+       /** Labels for calculated parameters */
+       private JLabel _fsUnitsLabel = null, _flatSpeedLabel = null;
+       private JLabel _climbUnitsLabel = null;
+       private JLabel _gentleClimbLabel = null, _steepClimbLabel = null;
+       private JLabel _descentUnitsLabel = null;
+       private JLabel _gentleDescentLabel = null, _steepDescentLabel = null;
+       private JLabel _averageErrorLabel = null;
+
+
+       /**
+        * Constructor
+        * @param inTitleKey key to use for title of panel
+        */
+       public ParametersPanel(String inTitleKey)
+       {
+               this(inTitleKey, false);
+       }
+
+       /**
+        * Constructor
+        * @param inTitleKey key to use for title of panel
+        * @param inShowAvgError true to show average error line
+        */
+       public ParametersPanel(String inTitleKey, boolean inShowAvgError)
+       {
+               super();
+               _showAverageError = inShowAvgError;
+               if (inTitleKey != null) {
+                       setBorder(BorderFactory.createTitledBorder(I18nManager.getText(inTitleKey)));
+               }
+               setLayout(new GridLayout(0, 3, 3, 3));
+               addLabels();
+       }
+
+       private void addLabels()
+       {
+               // flat speed
+               _fsUnitsLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + " 5km : ");
+               _fsUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+               add(_fsUnitsLabel);
+               _flatSpeedLabel = new JLabel("60 minutes"); // (filled in later)
+               add(_flatSpeedLabel);
+               add(new JLabel(""));
+               // Headers for gentle and steep
+               add(new JLabel(""));
+               JLabel gentleLabel = new JLabel(I18nManager.getText("dialog.estimatetime.gentle"));
+               add(gentleLabel);
+               JLabel steepLabel = new JLabel(I18nManager.getText("dialog.estimatetime.steep"));
+               add(steepLabel);
+               // Climb
+               _climbUnitsLabel = new JLabel("Climb 100m: ");
+               _climbUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+               add(_climbUnitsLabel);
+               _gentleClimbLabel = new JLabel("22 minutes"); // (filled in later)
+               add(_gentleClimbLabel);
+               _steepClimbLabel = new JLabel("22 minutes"); // (filled in later)
+               add(_steepClimbLabel);
+               // Descent
+               _descentUnitsLabel = new JLabel(I18nManager.getText("dialog.estimatetime.parameters.timefor") + ": ");
+               _descentUnitsLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+               add(_descentUnitsLabel);
+               _gentleDescentLabel = new JLabel("22 minutes"); // (filled in later)
+               add(_gentleDescentLabel);
+               _steepDescentLabel = new JLabel("22 minutes"); // (filled in later)
+               add(_steepDescentLabel);
+               // Average error
+               if (_showAverageError)
+               {
+                       JLabel errorLabel = new JLabel(I18nManager.getText("dialog.learnestimationparams.averageerror") + ": ");
+                       errorLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+                       add(errorLabel);
+                       _averageErrorLabel = new JLabel("22 minutes"); // (filled in later)
+                       add(_averageErrorLabel);
+               }
+       }
+
+       /**
+        * Update the labels using the given parameters
+        * @param inParams the parameters used or calculated
+        * @param inAverageError average error as percentage
+        * @param inShowError true to show this error value, false otherwise
+        */
+       private void updateParameters(EstimationParameters inParams, double inAverageError, boolean inShowError)
+       {
+               if (inParams == null || !inParams.isValid())
+               {
+                       _flatSpeedLabel.setText("");
+                       _gentleClimbLabel.setText(""); _steepClimbLabel.setText("");
+                       _gentleDescentLabel.setText(""); _steepDescentLabel.setText("");
+               }
+               else
+               {
+                       final String minsText = " " + I18nManager.getText("units.minutes");
+                       String[] values = inParams.getStrings(); // these strings are already formatted locally
+                       _fsUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
+                               " " + EstimationParameters.getStandardDistance() + ": ");
+                       _flatSpeedLabel.setText(values[0] + minsText);
+                       final String heightString = " " + EstimationParameters.getStandardClimb() + ": ";
+                       _climbUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.climb") + heightString);
+                       _gentleClimbLabel.setText(values[1] + minsText);
+                       _steepClimbLabel.setText(values[2] + minsText);
+                       _descentUnitsLabel.setText(I18nManager.getText("dialog.estimatetime.descent") + heightString);
+                       _gentleDescentLabel.setText(values[3] + minsText);
+                       _steepDescentLabel.setText(values[4] + minsText);
+               }
+               // Average error
+               if (_averageErrorLabel != null)
+               {
+                       if (inParams == null || !inParams.isValid() || !inShowError)
+                       {
+                               _averageErrorLabel.setText("");
+                       }
+                       else
+                       {
+                               _averageErrorLabel.setText(DisplayUtils.formatOneDp(inAverageError) + " %");
+                       }
+               }
+       }
+
+       /**
+        * Just show the parameters, with no average error
+        * @param inParams parameters to show
+        */
+       public void updateParameters(EstimationParameters inParams)
+       {
+               updateParameters(inParams, 0.0, false);
+       }
+
+       /**
+        * Show the parameters and the average error
+        * @param inParams parameters to show
+        * @param inAverageError average error as percentage
+        */
+       public void updateParameters(EstimationParameters inParams, double inAverageError)
+       {
+               updateParameters(inParams, inAverageError, true);
+       }
+}
diff --git a/tim/prune/function/estimate/jama/Maths.java b/tim/prune/function/estimate/jama/Maths.java
new file mode 100644 (file)
index 0000000..7c3326d
--- /dev/null
@@ -0,0 +1,31 @@
+package tim.prune.function.estimate.jama;
+
+/**
+ * Static helper method, taken from public domain NIST code for JAMA
+ */
+public abstract class Maths
+{
+       /**
+        * Work out sqrt(a^2 + b^2)
+        */
+       public static double pythag(double a, double b)
+       {
+               double r;
+
+               if (Math.abs(a) > Math.abs(b))
+               {
+                       r = b/a;
+                       r = Math.abs(a)*Math.sqrt(1+r*r);
+               }
+               else if (b != 0)
+               {
+                       r = a/b;
+                       r = Math.abs(b)*Math.sqrt(1+r*r);
+               }
+               else
+               {
+                       r = 0.0;
+               }
+               return r;
+       }
+}
diff --git a/tim/prune/function/estimate/jama/Matrix.java b/tim/prune/function/estimate/jama/Matrix.java
new file mode 100644 (file)
index 0000000..face1a9
--- /dev/null
@@ -0,0 +1,259 @@
+package tim.prune.function.estimate.jama;\r
+\r
+/**\r
+ * The Java Matrix Class provides the fundamental operations of numerical linear algebra.\r
+ * Original authors The MathWorks, Inc. and the National Institute of Standards and Technology\r
+ * The original public domain code has now been modified and reduced to only contain\r
+ * the use of QR Decomposition of rectangular matrices, to solve least squares regression,\r
+ * and is placed under GPL2 with the rest of the GpsPrune code.\r
+ */\r
+public class Matrix\r
+{\r
+\r
+       /** Array for internal storage of elements */\r
+       private double[][] _matrix;\r
+\r
+       /** Row and column dimensions */\r
+       private int _m, _n;\r
+\r
+\r
+       /**\r
+        * Construct an m-by-n matrix of zeros\r
+        * @param inM  Number of rows\r
+        * @param inN  Number of colums\r
+        */\r
+       public Matrix(int inM, int inN)\r
+       {\r
+               _m = inM;\r
+               _n = inN;\r
+               _matrix = new double[inM][inN];\r
+       }\r
+\r
+       /**\r
+        * Construct a matrix from a 2-D array\r
+        * @param A   Two-dimensional array of doubles.\r
+        * @exception IllegalArgumentException All rows must have the same length\r
+        */\r
+       public Matrix(double[][] A)\r
+       {\r
+               _m = A.length;\r
+               _n = A[0].length;\r
+               for (int i = 0; i < _m; i++) {\r
+                       if (A[i].length != _n) {\r
+                               throw new IllegalArgumentException("All rows must have the same length.");\r
+                       }\r
+               }\r
+               _matrix = A;\r
+       }\r
+\r
+       /**\r
+        * Construct a matrix quickly without checking arguments.\r
+        * @param inA   Two-dimensional array of doubles.\r
+        * @param inM   Number of rows\r
+        * @param inN   Number of columns\r
+        */\r
+       public Matrix(double[][] inA, int inM, int inN)\r
+       {\r
+               _matrix = inA;\r
+               _m = inM;\r
+               _n = inN;\r
+       }\r
+\r
+       /*\r
+        * ------------------------ Public Methods ------------------------\r
+        */\r
+\r
+\r
+       /**\r
+        * Set a value in a cell of the matrix\r
+        * @param inRow row index\r
+        * @param inCol column index\r
+        * @param inValue value to set\r
+        */\r
+       public void setValue(int inRow, int inCol, double inValue)\r
+       {\r
+               _matrix[inRow][inCol] = inValue;\r
+       }\r
+\r
+       /**\r
+        * Access the internal two-dimensional array.\r
+        * @return Pointer to the two-dimensional array of matrix elements.\r
+        */\r
+       public double[][] getArray() {\r
+               return _matrix;\r
+       }\r
+\r
+       /**\r
+        * Copy the internal two-dimensional array.\r
+        * @return Two-dimensional array copy of matrix elements.\r
+        */\r
+       public double[][] getArrayCopy()\r
+       {\r
+               double[][] C = new double[_m][_n];\r
+               for (int i = 0; i < _m; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               C[i][j] = _matrix[i][j];\r
+                       }\r
+               }\r
+               return C;\r
+       }\r
+\r
+       /**\r
+        * Get a single element.\r
+        * @param inRow   Row index\r
+        * @param inCol   Column index\r
+        * @return A(inRow,inCol)\r
+        * @exception ArrayIndexOutOfBoundsException\r
+        */\r
+       public double get(int inRow, int inCol) {\r
+               return _matrix[inRow][inCol];\r
+       }\r
+\r
+       /** @return number of rows _m */\r
+       public int getNumRows() {\r
+               return _m;\r
+       }\r
+\r
+       /** @return number of columns _n */\r
+       public int getNumColumns() {\r
+               return _n;\r
+       }\r
+\r
+       /**\r
+        * Get a submatrix\r
+        * @param i0  Initial row index\r
+        * @param i1  Final row index\r
+        * @param j0  Initial column index\r
+        * @param j1  Final column index\r
+        * @return A(i0:i1,j0:j1)\r
+        * @exception ArrayIndexOutOfBoundsException\r
+        */\r
+       public Matrix getMatrix(int i0, int i1, int j0, int j1)\r
+       {\r
+               Matrix X = new Matrix(i1 - i0 + 1, j1 - j0 + 1);\r
+               double[][] B = X.getArray();\r
+               try {\r
+                       for (int i = i0; i <= i1; i++) {\r
+                               for (int j = j0; j <= j1; j++) {\r
+                                       B[i - i0][j - j0] = _matrix[i][j];\r
+                               }\r
+                       }\r
+               } catch (ArrayIndexOutOfBoundsException e) {\r
+                       throw new ArrayIndexOutOfBoundsException("Submatrix indices");\r
+               }\r
+               return X;\r
+       }\r
+\r
+\r
+       /**\r
+        * Linear algebraic matrix multiplication, A * B\r
+        * @param B   another matrix\r
+        * @return Matrix product, A * B\r
+        * @exception IllegalArgumentException if matrix dimensions don't agree\r
+        */\r
+       public Matrix times(Matrix B)\r
+       {\r
+               if (B._m != _n) {\r
+                       throw new IllegalArgumentException("Matrix inner dimensions must agree.");\r
+               }\r
+               Matrix X = new Matrix(_m, B._n);\r
+               double[][] C = X.getArray();\r
+               double[] Bcolj = new double[_n];\r
+               for (int j = 0; j < B._n; j++) {\r
+                       for (int k = 0; k < _n; k++) {\r
+                               Bcolj[k] = B._matrix[k][j];\r
+                       }\r
+                       for (int i = 0; i < _m; i++) {\r
+                               double[] Arowi = _matrix[i];\r
+                               double s = 0;\r
+                               for (int k = 0; k < _n; k++) {\r
+                                       s += Arowi[k] * Bcolj[k];\r
+                               }\r
+                               C[i][j] = s;\r
+                       }\r
+               }\r
+               return X;\r
+       }\r
+\r
+       /**\r
+        * Subtract the other matrix from this one\r
+        * @param B   another matrix\r
+        * @return difference this - B\r
+        * @exception IllegalArgumentException if matrix dimensions don't agree\r
+        */\r
+       public Matrix minus(Matrix B)\r
+       {\r
+               if (B._m != _m || B._n != _n) {\r
+                       throw new IllegalArgumentException("Matrix dimensions must agree.");\r
+               }\r
+               Matrix result = new Matrix(_m, _n);\r
+               for (int i = 0; i < _m; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               result.setValue(i, j, get(i, j) - B.get(i, j));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+\r
+       /**\r
+        * Divide each element of this matrix by the corresponding element in the other one\r
+        * @param B   another matrix\r
+        * @return this[i,j]/other[i,j]\r
+        * @exception IllegalArgumentException if matrix dimensions don't agree\r
+        */\r
+       public Matrix divideEach(Matrix B)\r
+       {\r
+               if (B._m != _m || B._n != _n) {\r
+                       throw new IllegalArgumentException("Matrix dimensions must agree.");\r
+               }\r
+               Matrix result = new Matrix(_m, _n);\r
+               for (int i = 0; i < _m; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               result.setValue(i, j, get(i, j) / B.get(i, j));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+\r
+       /**\r
+        * Solve A*X = B\r
+        * @param B   right hand side\r
+        * @return least squares solution\r
+        */\r
+       public Matrix solve(Matrix B) {\r
+               return new QRDecomposition(this).solve(B);\r
+       }\r
+\r
+       /**\r
+        * @return the average absolute value of all the elements in the matrix\r
+        */\r
+       public double getAverageAbsValue()\r
+       {\r
+               double total = 0.0;\r
+               for (int i = 0; i < _m; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               total += Math.abs(_matrix[i][j]);\r
+                       }\r
+               }\r
+               return total / _m / _n;\r
+       }\r
+\r
+       /**\r
+        * Primitive output for debugging\r
+        */\r
+       public String toString()\r
+       {\r
+               StringBuilder builder = new StringBuilder();\r
+               builder.append('(');\r
+               for (int i = 0; i < _m; i++) {\r
+                       builder.append('(');\r
+                       for (int j = 0; j < _n; j++) {\r
+                               builder.append((_matrix[i][j]));\r
+                               builder.append(", ");\r
+                       }\r
+                       builder.append(") ");\r
+               }\r
+               builder.append(')');\r
+               return builder.toString();\r
+       }\r
+}\r
diff --git a/tim/prune/function/estimate/jama/QRDecomposition.java b/tim/prune/function/estimate/jama/QRDecomposition.java
new file mode 100644 (file)
index 0000000..e8aa2b7
--- /dev/null
@@ -0,0 +1,219 @@
+package tim.prune.function.estimate.jama;\r
+\r
+/**\r
+ * QR Decomposition.\r
+ *\r
+ * For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n\r
+ * orthogonal matrix Q and an n-by-n upper triangular matrix R so that A = Q*R.\r
+ *\r
+ * The QR decomposition always exists, even if the matrix does not have full\r
+ * rank, so the constructor will never fail. The primary use of the QR\r
+ * decomposition is in the least squares solution of nonsquare systems of\r
+ * simultaneous linear equations. This will fail if isFullRank() returns false.\r
+ *\r
+ * Original authors The MathWorks, Inc. and the National Institute of Standards and Technology\r
+ * The original public domain code has now been modified and reduced,\r
+ * and is placed under GPL2 with the rest of the GpsPrune code.\r
+ */\r
+public class QRDecomposition\r
+{\r
+\r
+       /** Array for internal storage of decomposition */\r
+       private double[][] _QR;\r
+\r
+       /** Row and column dimensions */\r
+       private int _m, _n;\r
+\r
+       /** Array for internal storage of diagonal of R */\r
+       private double[] _Rdiag;\r
+\r
+\r
+       /**\r
+        * QR Decomposition, computed by Householder reflections.\r
+        *\r
+        * @param inA   Rectangular matrix\r
+        * @return Structure to access R and the Householder vectors and compute Q.\r
+        */\r
+       public QRDecomposition(Matrix inA)\r
+       {\r
+               // Initialize.\r
+               _QR = inA.getArrayCopy();\r
+               _m = inA.getNumRows();\r
+               _n = inA.getNumColumns();\r
+               _Rdiag = new double[_n];\r
+\r
+               // Main loop.\r
+               for (int k = 0; k < _n; k++)\r
+               {\r
+                       // Compute 2-norm of k-th column without under/overflow.\r
+                       double nrm = 0;\r
+                       for (int i = k; i < _m; i++) {\r
+                               nrm = Maths.pythag(nrm, _QR[i][k]);\r
+                       }\r
+\r
+                       if (nrm != 0.0)\r
+                       {\r
+                               // Form k-th Householder vector.\r
+                               if (_QR[k][k] < 0) {\r
+                                       nrm = -nrm;\r
+                               }\r
+                               for (int i = k; i < _m; i++) {\r
+                                       _QR[i][k] /= nrm;\r
+                               }\r
+                               _QR[k][k] += 1.0;\r
+\r
+                               // Apply transformation to remaining columns.\r
+                               for (int j = k + 1; j < _n; j++)\r
+                               {\r
+                                       double s = 0.0;\r
+                                       for (int i = k; i < _m; i++) {\r
+                                               s += _QR[i][k] * _QR[i][j];\r
+                                       }\r
+                                       s = -s / _QR[k][k];\r
+                                       for (int i = k; i < _m; i++) {\r
+                                               _QR[i][j] += s * _QR[i][k];\r
+                                       }\r
+                               }\r
+                       }\r
+                       _Rdiag[k] = -nrm;\r
+               }\r
+       }\r
+\r
+       /*\r
+        * ------------------------ Public Methods ------------------------\r
+        */\r
+\r
+       /**\r
+        * Is the matrix full rank?\r
+        * @return true if R, and hence A, has full rank.\r
+        */\r
+       public boolean isFullRank()\r
+       {\r
+               for (int j = 0; j < _n; j++) {\r
+                       if (_Rdiag[j] == 0)\r
+                               return false;\r
+               }\r
+               return true;\r
+       }\r
+\r
+       /**\r
+        * Return the Householder vectors\r
+        * @deprecated\r
+        * @return Lower trapezoidal matrix whose columns define the reflections\r
+        */\r
+       private Matrix getH()\r
+       {\r
+               Matrix X = new Matrix(_m, _n);\r
+               double[][] H = X.getArray();\r
+               for (int i = 0; i < _m; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               if (i >= j) {\r
+                                       H[i][j] = _QR[i][j];\r
+                               } else {\r
+                                       H[i][j] = 0.0;\r
+                               }\r
+                       }\r
+               }\r
+               return X;\r
+       }\r
+\r
+       /**\r
+        * Return the upper triangular factor\r
+        * @deprecated\r
+        * @return R\r
+        */\r
+       private Matrix getR()\r
+       {\r
+               Matrix X = new Matrix(_n, _n);\r
+               double[][] R = X.getArray();\r
+               for (int i = 0; i < _n; i++) {\r
+                       for (int j = 0; j < _n; j++) {\r
+                               if (i < j) {\r
+                                       R[i][j] = _QR[i][j];\r
+                               } else if (i == j) {\r
+                                       R[i][j] = _Rdiag[i];\r
+                               } else {\r
+                                       R[i][j] = 0.0;\r
+                               }\r
+                       }\r
+               }\r
+               return X;\r
+       }\r
+\r
+       /**\r
+        * Generate and return the (economy-sized) orthogonal factor\r
+        * @deprecated\r
+        * @return Q\r
+        */\r
+       private Matrix getQ()\r
+       {\r
+               Matrix X = new Matrix(_m, _n);\r
+               double[][] Q = X.getArray();\r
+               for (int k = _n - 1; k >= 0; k--) {\r
+                       for (int i = 0; i < _m; i++) {\r
+                               Q[i][k] = 0.0;\r
+                       }\r
+                       Q[k][k] = 1.0;\r
+                       for (int j = k; j < _n; j++) {\r
+                               if (_QR[k][k] != 0) {\r
+                                       double s = 0.0;\r
+                                       for (int i = k; i < _m; i++) {\r
+                                               s += _QR[i][k] * Q[i][j];\r
+                                       }\r
+                                       s = -s / _QR[k][k];\r
+                                       for (int i = k; i < _m; i++) {\r
+                                               Q[i][j] += s * _QR[i][k];\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               return X;\r
+       }\r
+\r
+       /**\r
+        * Least squares solution of A*X = B\r
+        * @param B   A Matrix with as many rows as A and any number of columns\r
+        * @return X that minimizes the two norm of Q*R*X-B\r
+        * @exception IllegalArgumentException if matrix dimensions don't agree\r
+        * @exception RuntimeException         if Matrix is rank deficient.\r
+        */\r
+       public Matrix solve(Matrix B)\r
+       {\r
+               if (B.getNumRows() != _m) {\r
+                       throw new IllegalArgumentException("Matrix row dimensions must agree.");\r
+               }\r
+               if (!isFullRank()) {\r
+                       throw new RuntimeException("Matrix is rank deficient.");\r
+               }\r
+\r
+               // Copy right hand side\r
+               int nx = B.getNumColumns();\r
+               double[][] X = B.getArrayCopy();\r
+\r
+               // Compute Y = transpose(Q)*B\r
+               for (int k = 0; k < _n; k++) {\r
+                       for (int j = 0; j < nx; j++) {\r
+                               double s = 0.0;\r
+                               for (int i = k; i < _m; i++) {\r
+                                       s += _QR[i][k] * X[i][j];\r
+                               }\r
+                               s = -s / _QR[k][k];\r
+                               for (int i = k; i < _m; i++) {\r
+                                       X[i][j] += s * _QR[i][k];\r
+                               }\r
+                       }\r
+               }\r
+               // Solve R*X = Y;\r
+               for (int k = _n - 1; k >= 0; k--) {\r
+                       for (int j = 0; j < nx; j++) {\r
+                               X[k][j] /= _Rdiag[k];\r
+                       }\r
+                       for (int i = 0; i < k; i++) {\r
+                               for (int j = 0; j < nx; j++) {\r
+                                       X[i][j] -= X[k][j] * _QR[i][k];\r
+                               }\r
+                       }\r
+               }\r
+               return (new Matrix(X, _n, nx).getMatrix(0, _n - 1, 0, nx - 1));\r
+       }\r
+}\r
index cf634c0fbc6b48b932382d21b3695b9e30ddc575..286ba1aae7bc677cd835e3855ea213d44e206b6d 100644 (file)
@@ -40,6 +40,8 @@ public abstract class GenericDownloaderFunction extends GenericFunction implemen
        protected JTable _trackTable = null;
        /** Cancelled flag */
        protected boolean _cancelled = false;
        protected JTable _trackTable = null;
        /** Cancelled flag */
        protected boolean _cancelled = false;
+       /** error message */
+       protected String _errorMessage = null;
        /** Status label */
        protected JLabel _statusLabel = null;
        /** Description box */
        /** Status label */
        protected JLabel _statusLabel = null;
        /** Description box */
@@ -84,6 +86,7 @@ public abstract class GenericDownloaderFunction extends GenericFunction implemen
                _showButton.setEnabled(false);
                _cancelled = false;
                _descriptionBox.setText("");
                _showButton.setEnabled(false);
                _cancelled = false;
                _descriptionBox.setText("");
+               _errorMessage = null;
                // Start new thread to load list asynchronously
                new Thread(this).start();
 
                // Start new thread to load list asynchronously
                new Thread(this).start();
 
index 904dd8bc62248d49fe4e5669ee37fccc19a643a3..274c0b882a3bf51f1ee7c7dfe278646f756ff235 100644 (file)
@@ -57,6 +57,12 @@ public class TrackListModel extends AbstractTableModel
                return _trackList.size();
        }
 
                return _trackList.size();
        }
 
+       /** @return true if there are no rows */
+       public boolean isEmpty()
+       {
+               return getRowCount() == 0;
+       }
+
        /**
         * @param inColNum column number
         * @return column label for given column
        /**
         * @param inColNum column number
         * @return column label for given column
index 586cbb6d72eede3c5a62d77b68c8649b8845c5d8..9a4ae30b7d4aa3d5c2506dd5c496d86a2485fad3 100644 (file)
@@ -182,7 +182,8 @@ public class UploadGpsiesFunction extends GenericFunction
                                enableOK();
                        }
                };
                                enableOK();
                        }
                };
-               GuiGridLayout actGrid = new GuiGridLayout(activityPanel, true);
+               // Why not a simple grid layout here?
+               GuiGridLayout actGrid = new GuiGridLayout(activityPanel, new double[] {1.0, 1.0}, new boolean[] {false, false});
                final int numActivities = ACTIVITY_KEYS.length;
                _activityCheckboxes = new JCheckBox[numActivities];
                for (int i=0; i<numActivities; i++)
                final int numActivities = ACTIVITY_KEYS.length;
                _activityCheckboxes = new JCheckBox[numActivities];
                for (int i=0; i<numActivities; i++)
index 22ee8311a11c91222f58475dfdd2df6e0f750ca2..4d13348d3c4be742656f3b3690c7a9db09609ca8 100644 (file)
@@ -1,23 +1,12 @@
 package tim.prune.function.srtm;
 
 package tim.prune.function.srtm;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
 
 import tim.prune.App;
 import tim.prune.DataSubscriber;
 
 import tim.prune.App;
 import tim.prune.DataSubscriber;
@@ -27,6 +16,7 @@ import tim.prune.UpdateMessageBroker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Track;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Track;
+import tim.prune.gui.ProgressDialog;
 import tim.prune.undo.UndoLookupSrtm;
 
 /**
 import tim.prune.undo.UndoLookupSrtm;
 
 /**
@@ -37,12 +27,8 @@ import tim.prune.undo.UndoLookupSrtm;
  */
 public class LookupSrtmFunction extends GenericFunction implements Runnable
 {
  */
 public class LookupSrtmFunction extends GenericFunction implements Runnable
 {
-       /** function dialog */
-       private JDialog _dialog = null;
-       /** Progress bar for function */
-       private JProgressBar _progressBar = null;
-       /** Cancel flag */
-       private boolean _cancelled = false;
+       /** Progress dialog */
+       ProgressDialog _progress = null;
 
        /** Expected size of hgt file in bytes */
        private static final long HGT_SIZE = 2884802L;
 
        /** Expected size of hgt file in bytes */
        private static final long HGT_SIZE = 2884802L;
@@ -69,49 +55,16 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
         */
        public void begin()
        {
         */
        public void begin()
        {
-               if (_dialog == null)
+               if (_progress == null)
                {
                {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), false);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.getContentPane().add(makeDialogComponents());
-                       _dialog.pack();
+                       _progress = new ProgressDialog(_parentFrame, getNameKey());
                }
                }
-               _progressBar.setMinimum(0);
-               _progressBar.setMaximum(100);
-               _progressBar.setValue(20);
-               _cancelled = false;
+               _progress.show();
                // start new thread for time-consuming part
                new Thread(this).start();
        }
 
 
                // start new thread for time-consuming part
                new Thread(this).start();
        }
 
 
-       /**
-        * Make the dialog components
-        * @return the GUI components for the dialog
-        */
-       private Component makeDialogComponents()
-       {
-               JPanel dialogPanel = new JPanel();
-               dialogPanel.setLayout(new BorderLayout());
-               dialogPanel.add(new JLabel(I18nManager.getText("confirm.running")), BorderLayout.NORTH);
-               _progressBar = new JProgressBar();
-               _progressBar.setPreferredSize(new Dimension(250, 30));
-               dialogPanel.add(_progressBar, BorderLayout.CENTER);
-               // Cancel button at the bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               _cancelled = true;
-                               _dialog.dispose();
-                       }
-               });
-               buttonPanel.add(cancelButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               return dialogPanel;
-       }
-
        /**
         * Run method using separate thread
         */
        /**
         * Run method using separate thread
         */
@@ -145,7 +98,6 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                        overwriteZeros = true;
                }
 
                        overwriteZeros = true;
                }
 
-               _dialog.setVisible(true);
                // Now loop again to extract the required tiles
                for (int i=0; i<track.getNumPoints(); i++)
                {
                // Now loop again to extract the required tiles
                for (int i=0; i<track.getNumPoints(); i++)
                {
@@ -176,20 +128,19 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                UndoLookupSrtm undo = new UndoLookupSrtm(_app.getTrackInfo());
                int numAltitudesFound = 0;
                // Update progress bar
                UndoLookupSrtm undo = new UndoLookupSrtm(_app.getTrackInfo());
                int numAltitudesFound = 0;
                // Update progress bar
-               _progressBar.setMaximum(inTileList.size());
-               _progressBar.setIndeterminate(inTileList.size() <= 1);
-               _progressBar.setValue(0);
+               _progress.setMaximum(inTileList.size());
+               _progress.setValue(0);
                String errorMessage = null;
                // Get urls for each tile
                URL[] urls = TileFinder.getUrls(inTileList);
                String errorMessage = null;
                // Get urls for each tile
                URL[] urls = TileFinder.getUrls(inTileList);
-               for (int t=0; t<inTileList.size() && !_cancelled; t++)
+               for (int t=0; t<inTileList.size() && !_progress.isCancelled(); t++)
                {
                        if (urls[t] != null)
                        {
                                SrtmTile tile = inTileList.get(t);
                                try
                                {
                {
                        if (urls[t] != null)
                        {
                                SrtmTile tile = inTileList.get(t);
                                try
                                {
-                                       _progressBar.setValue(t);
+                                       _progress.setValue(t);
                                        final int ARRLENGTH = 1201*1201;
                                        int[] heights = new int[ARRLENGTH];
                                        // Open zipinputstream on url and check size
                                        final int ARRLENGTH = 1201*1201;
                                        int[] heights = new int[ARRLENGTH];
                                        // Open zipinputstream on url and check size
@@ -253,8 +204,8 @@ public class LookupSrtmFunction extends GenericFunction implements Runnable
                                }
                        }
                }
                                }
                        }
                }
-               _dialog.dispose();
-               if (_cancelled) {return;}
+               _progress.dispose();
+               if (_progress.isCancelled()) {return;}
                if (numAltitudesFound > 0)
                {
                        // Inform app including undo information
                if (numAltitudesFound > 0)
                {
                        // Inform app including undo information
index 209db7f58d439208caa363097236f40805957640..afcc28f274a2634b3bb74de613fa4cb653630a92 100644 (file)
@@ -54,10 +54,11 @@ public abstract class TileFinder
         */
        private static byte[] readDatFile()
        {
         */
        private static byte[] readDatFile()
        {
+               InputStream in = null;
                try
                {
                        // Need absolute path to dat file
                try
                {
                        // Need absolute path to dat file
-                       InputStream in = TileFinder.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
+                       in = TileFinder.class.getResourceAsStream("/tim/prune/function/srtm/srtmtiles.dat");
                        if (in != null)
                        {
                                byte[] buffer = new byte[in.available()];
                        if (in != null)
                        {
                                byte[] buffer = new byte[in.available()];
@@ -69,6 +70,13 @@ public abstract class TileFinder
                catch (java.io.IOException e) {
                        System.err.println("Exception trying to read srtmtiles.dat : " + e.getMessage());
                }
                catch (java.io.IOException e) {
                        System.err.println("Exception trying to read srtmtiles.dat : " + e.getMessage());
                }
+               finally
+               {
+                       try {
+                               in.close();
+                       }
+                       catch (Exception e) {} // ignore
+               }
                return null;
        }
 }
                return null;
        }
 }
diff --git a/tim/prune/gui/DecimalNumberField.java b/tim/prune/gui/DecimalNumberField.java
new file mode 100644 (file)
index 0000000..f1baa2a
--- /dev/null
@@ -0,0 +1,111 @@
+package tim.prune.gui;
+
+import java.awt.Dimension;
+
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * text field for holding a decimal number with validation
+ * - doesn't allow certain characters such as a-z to be entered
+ */
+public class DecimalNumberField extends JTextField
+{
+       /**
+        * Inner class to act as document for validation
+        */
+       protected static class DecimalNumberDocument extends PlainDocument
+       {
+               private boolean _allowNegative = false;
+
+               /** constructor */
+               DecimalNumberDocument(boolean inAllowNegative) {
+                       _allowNegative = inAllowNegative;
+               }
+
+               /**
+                * Override the insert string method
+                * @param offs offset
+                * @param str string
+                * @param a attributes
+                * @throws BadLocationException on insert failure
+                */
+               public void insertString(int offs, String str, AttributeSet a)
+                       throws BadLocationException
+               {
+                       char[] source = str.toCharArray();
+                       char[] result = new char[source.length];
+                       int j = 0;
+                       for (int i = 0; i < result.length; i++) {
+                               if (!Character.isLetter(source[i]) && (_allowNegative || source[i] != '-') && source[i] != ' ') // no letters, no minus sign or space
+                                       result[j++] = source[i];
+                       }
+                       super.insertString(offs, new String(result, 0, j), a);
+               }
+       }
+
+
+       /**
+        * Constructor
+        */
+       public DecimalNumberField()
+       {
+               super(6);
+               setDocument(new DecimalNumberDocument(false));
+       }
+
+       /**
+        * Constructor
+        * @param inAllowNegative true to allow negative numbers
+        */
+       public DecimalNumberField(boolean inAllowNegative)
+       {
+               super(6);
+               setDocument(new DecimalNumberDocument(inAllowNegative));
+       }
+
+       /**
+        * @return double value
+        */
+       public double getValue()
+       {
+               return parseValue(getText());
+       }
+
+       /**
+        * @param inValue value to set
+        */
+       public void setValue(double inValue)
+       {
+               setText("" + inValue);
+       }
+
+       /**
+        * @param inText text to parse
+        * @return value as double
+        */
+       private static double parseValue(String inText)
+       {
+               double value = 0.0;
+               try {
+                       value = Double.parseDouble(inText);
+               }
+               catch (NumberFormatException nfe) {}
+               if (value < 0) {
+                       value = 0;
+               }
+               return value;
+       }
+
+       /**
+        * Put a minimum on the minimum width
+        */
+       public Dimension getMinimumSize()
+       {
+               Dimension dim = super.getMinimumSize();
+               if (dim.width < 50) dim.width = 50;
+               return dim;
+       }
+}
index 7b3a4ff6843f9b34c4c7df048081a54f92ec17e6..13df7cd31609b0ea8ef1eb6b7fdb59d6639a722d 100644 (file)
@@ -8,7 +8,6 @@ import java.awt.Font;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.text.NumberFormat;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
@@ -51,6 +50,7 @@ public class DetailsDisplay extends GenericDisplay
        private JLabel _latLabel = null, _longLabel = null;
        private JLabel _altLabel = null;
        private JLabel _timeLabel = null;
        private JLabel _latLabel = null, _longLabel = null;
        private JLabel _altLabel = null;
        private JLabel _timeLabel = null;
+       private JLabel _descLabel = null;
        private JLabel _speedLabel = null, _vSpeedLabel = null;
        private JLabel _nameLabel = null, _typeLabel = null;
 
        private JLabel _speedLabel = null, _vSpeedLabel = null;
        private JLabel _nameLabel = null, _typeLabel = null;
 
@@ -84,8 +84,6 @@ public class DetailsDisplay extends GenericDisplay
        // Units
        private JComboBox _coordFormatDropdown = null;
        private JComboBox _distUnitsDropdown = null;
        // Units
        private JComboBox _coordFormatDropdown = null;
        private JComboBox _distUnitsDropdown = null;
-       // Formatter
-       private NumberFormat _distanceFormatter = NumberFormat.getInstance();
 
        // Cached labels
        private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
 
        // Cached labels
        private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
@@ -95,6 +93,7 @@ public class DetailsDisplay extends GenericDisplay
        private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
        private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
        private static final String LABEL_POINT_WAYPOINTTYPE = I18nManager.getText("fieldname.waypointtype") + ": ";
        private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
        private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
        private static final String LABEL_POINT_WAYPOINTTYPE = I18nManager.getText("fieldname.waypointtype") + ": ";
+       private static final String LABEL_POINT_DESCRIPTION  = I18nManager.getText("fieldname.description") + ": ";
        private static final String LABEL_POINT_SPEED        = I18nManager.getText("fieldname.speed") + ": ";
        private static final String LABEL_POINT_VERTSPEED    = I18nManager.getText("fieldname.verticalspeed") + ": ";
        private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": ";
        private static final String LABEL_POINT_SPEED        = I18nManager.getText("fieldname.speed") + ": ";
        private static final String LABEL_POINT_VERTSPEED    = I18nManager.getText("fieldname.verticalspeed") + ": ";
        private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": ";
@@ -135,6 +134,8 @@ public class DetailsDisplay extends GenericDisplay
                _timeLabel = new JLabel("");
                _timeLabel.setMinimumSize(new Dimension(120, 10));
                pointDetailsPanel.add(_timeLabel);
                _timeLabel = new JLabel("");
                _timeLabel.setMinimumSize(new Dimension(120, 10));
                pointDetailsPanel.add(_timeLabel);
+               _descLabel = new JLabel("");
+               pointDetailsPanel.add(_descLabel);
                _speedLabel = new JLabel("");
                pointDetailsPanel.add(_speedLabel);
                _vSpeedLabel = new JLabel("");
                _speedLabel = new JLabel("");
                pointDetailsPanel.add(_speedLabel);
                _vSpeedLabel = new JLabel("");
@@ -296,6 +297,7 @@ public class DetailsDisplay extends GenericDisplay
                        _longLabel.setText("");
                        _altLabel.setText("");
                        _timeLabel.setText("");
                        _longLabel.setText("");
                        _altLabel.setText("");
                        _timeLabel.setText("");
+                       _descLabel.setText("");
                        _nameLabel.setText("");
                        _typeLabel.setText("");
                        _speedLabel.setText("");
                        _nameLabel.setText("");
                        _typeLabel.setText("");
                        _speedLabel.setText("");
@@ -315,9 +317,27 @@ public class DetailsDisplay extends GenericDisplay
                                : "");
                        if (currentPoint.hasTimestamp()) {
                                _timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
                                : "");
                        if (currentPoint.hasTimestamp()) {
                                _timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
+                               _timeLabel.setToolTipText(currentPoint.getTimestamp().getText());
                        }
                        else {
                                _timeLabel.setText("");
                        }
                        else {
                                _timeLabel.setText("");
+                               _timeLabel.setToolTipText("");
+                       }
+                       // Maybe the point has a description?
+                       String pointDesc = currentPoint.getFieldValue(Field.DESCRIPTION);
+                       if (pointDesc == null || pointDesc.equals("") || currentPoint.hasMedia()) {
+                               _descLabel.setText("");
+                               _descLabel.setToolTipText("");
+                       }
+                       else
+                       {
+                               if (pointDesc.length() < 5) {
+                                       _descLabel.setText(LABEL_POINT_DESCRIPTION + pointDesc);
+                               }
+                               else {
+                                       _descLabel.setText(shortenString(pointDesc));
+                               }
+                               _descLabel.setToolTipText(pointDesc);
                        }
 
                        // Speed can come from either timestamps and distances, or speed values in data
                        }
 
                        // Speed can come from either timestamps and distances, or speed values in data
@@ -325,7 +345,7 @@ public class DetailsDisplay extends GenericDisplay
                        SpeedCalculator.calculateSpeed(_track, currentPointIndex, speedValue);
                        if (speedValue.isValid())
                        {
                        SpeedCalculator.calculateSpeed(_track, currentPointIndex, speedValue);
                        if (speedValue.isValid())
                        {
-                               String speed = roundedNumber(speedValue.getValue()) + " " + speedUnitsStr;
+                               String speed = DisplayUtils.roundedNumber(speedValue.getValue()) + " " + speedUnitsStr;
                                _speedLabel.setText(LABEL_POINT_SPEED + speed);
                        }
                        else {
                                _speedLabel.setText(LABEL_POINT_SPEED + speed);
                        }
                        else {
@@ -337,7 +357,7 @@ public class DetailsDisplay extends GenericDisplay
                        if (speedValue.isValid())
                        {
                                String vSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
                        if (speedValue.isValid())
                        {
                                String vSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
-                               String speed = roundedNumber(speedValue.getValue()) + " " + vSpeedUnitsStr;
+                               String speed = DisplayUtils.roundedNumber(speedValue.getValue()) + " " + vSpeedUnitsStr;
                                _vSpeedLabel.setText(LABEL_POINT_VERTSPEED + speed);
                        }
                        else {
                                _vSpeedLabel.setText(LABEL_POINT_VERTSPEED + speed);
                        }
                        else {
@@ -374,12 +394,12 @@ public class DetailsDisplay extends GenericDisplay
                        _rangeLabel.setText(LABEL_RANGE_SELECTED
                                + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to")
                                + " " + (selection.getEnd()+1));
                        _rangeLabel.setText(LABEL_RANGE_SELECTED
                                + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to")
                                + " " + (selection.getEnd()+1));
-                       _distanceLabel.setText(LABEL_RANGE_DISTANCE + roundedNumber(selection.getDistance()) + " " + distUnitsStr);
+                       _distanceLabel.setText(LABEL_RANGE_DISTANCE + DisplayUtils.roundedNumber(selection.getDistance()) + " " + distUnitsStr);
                        if (selection.getNumSeconds() > 0)
                        {
                                _durationLabel.setText(LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(selection.getNumSeconds()));
                                _aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": "
                        if (selection.getNumSeconds() > 0)
                        {
                                _durationLabel.setText(LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(selection.getNumSeconds()));
                                _aveSpeedLabel.setText(I18nManager.getText("details.range.avespeed") + ": "
-                                       + roundedNumber(selection.getDistance()/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
+                                       + DisplayUtils.roundedNumber(selection.getDistance()/selection.getNumSeconds()*3600.0) + " " + speedUnitsStr);
                        }
                        else {
                                _durationLabel.setText("");
                        }
                        else {
                                _durationLabel.setText("");
@@ -502,27 +522,6 @@ public class DetailsDisplay extends GenericDisplay
        }
 
 
        }
 
 
-       /**
-        * Format a number to a sensible precision
-        * @param inDist distance
-        * @return formatted String
-        */
-       private String roundedNumber(double inDist)
-       {
-               // Set precision of formatter
-               int numDigits = 0;
-               if (inDist < 1.0)
-                       numDigits = 3;
-               else if (inDist < 10.0)
-                       numDigits = 2;
-               else if (inDist < 100.0)
-                       numDigits = 1;
-               // set formatter
-               _distanceFormatter.setMaximumFractionDigits(numDigits);
-               _distanceFormatter.setMinimumFractionDigits(numDigits);
-               return _distanceFormatter.format(inDist);
-       }
-
        /**
         * Restrict the given coordinate to a limited number of decimal places for display
         * @param inCoord coordinate string
        /**
         * Restrict the given coordinate to a limited number of decimal places for display
         * @param inCoord coordinate string
@@ -582,15 +581,26 @@ public class DetailsDisplay extends GenericDisplay
         */
        private static String shortenPath(String inFullPath)
        {
         */
        private static String shortenPath(String inFullPath)
        {
+               String path = inFullPath;
                // Chop off the home path if possible
                final String homePath = System.getProperty("user.home").toLowerCase();
                if (inFullPath != null && inFullPath.toLowerCase().startsWith(homePath)) {
                // Chop off the home path if possible
                final String homePath = System.getProperty("user.home").toLowerCase();
                if (inFullPath != null && inFullPath.toLowerCase().startsWith(homePath)) {
-                       inFullPath = inFullPath.substring(homePath.length()+1);
+                       path = inFullPath.substring(homePath.length()+1);
                }
                }
-               if (inFullPath == null || inFullPath.length() < 21) {
-                       return inFullPath;
+               return shortenString(path);
+       }
+
+       /**
+        * @param inString string to shorten
+        * @return shortened string from the beginning
+        */
+       private static String shortenString(String inString)
+       {
+               // Limit is hardcoded here, maybe it should depend on parent component width and font size etc?
+               if (inString == null || inString.length() < 21) {
+                       return inString;
                }
                }
-               // path is too long
-               return inFullPath.substring(0, 20) + "...";
+               // string is too long
+               return inString.substring(0, 20) + "...";
        }
 }
        }
 }
index df706b74e17c754661ebc164b22a8adf2724b56c..49ca3870b6304ccc3bc2c40f56b18d2b3685a6fe 100644 (file)
@@ -1,5 +1,7 @@
 package tim.prune.gui;
 
 package tim.prune.gui;
 
+import java.text.NumberFormat;
+
 import tim.prune.I18nManager;
 
 /**
 import tim.prune.I18nManager;
 
 /**
@@ -7,6 +9,19 @@ import tim.prune.I18nManager;
  */
 public abstract class DisplayUtils
 {
  */
 public abstract class DisplayUtils
 {
+       /** Number formatter for one decimal place */
+       private static final NumberFormat FORMAT_ONE_DP = NumberFormat.getNumberInstance();
+
+       /** Static block to initialise the one d.p. formatter */
+       static
+       {
+               FORMAT_ONE_DP.setMaximumFractionDigits(1);
+               FORMAT_ONE_DP.setMinimumFractionDigits(1);
+       }
+       /** Flexible number formatter with different decimal places */
+       private static final NumberFormat FORMAT_FLEX = NumberFormat.getNumberInstance();
+
+
        /**
         * Build a String to describe a time duration
         * @param inNumSecs number of seconds
        /**
         * Build a String to describe a time duration
         * @param inNumSecs number of seconds
@@ -25,4 +40,34 @@ public abstract class DisplayUtils
                if (inNumSecs < 86400000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
                return "big";
        }
                if (inNumSecs < 86400000L) return "" + (inNumSecs / 86400L) + I18nManager.getText("display.range.time.days");
                return "big";
        }
+
+       /**
+        * @param inNumber number to format
+        * @return formatted number to one decimal place
+        */
+       public static String formatOneDp(double inNumber)
+       {
+               return FORMAT_ONE_DP.format(inNumber);
+       }
+
+       /**
+        * Format a number to a sensible precision
+        * @param inVal value to format
+        * @return formatted String using local format
+        */
+       public static String roundedNumber(double inVal)
+       {
+               // Set precision of formatter
+               int numDigits = 0;
+               if (inVal < 1.0)
+                       numDigits = 3;
+               else if (inVal < 10.0)
+                       numDigits = 2;
+               else if (inVal < 100.0)
+                       numDigits = 1;
+               // set formatter
+               FORMAT_FLEX.setMaximumFractionDigits(numDigits);
+               FORMAT_FLEX.setMinimumFractionDigits(numDigits);
+               return FORMAT_FLEX.format(inVal);
+       }
 }
 }
index 7b226b5f9af0afc48d1f18384f27d2b859bcd6f8..703f7dd1d1be00f6cca07525fe54ae28e9db125f 100644 (file)
@@ -8,15 +8,17 @@ import javax.swing.JComponent;
 import javax.swing.JPanel;
 
 /**
 import javax.swing.JPanel;
 
 /**
- * Class to make it easier to use GridBagLayout
- * for a two-column, non-equal-width layout
+ * Class to make it easier to use GridBagLayout for a non-equal-width layout
+ * Default is two columns but can handle more
  */
 public class GuiGridLayout
 {
        private GridBagLayout _layout = null;
        private GridBagConstraints _constraints = null;
        private JPanel _panel = null;
  */
 public class GuiGridLayout
 {
        private GridBagLayout _layout = null;
        private GridBagConstraints _constraints = null;
        private JPanel _panel = null;
-       private boolean _allLeft = false;
+       private int _numColumns = 0;
+       private double[] _colWeights = null;
+       private boolean[] _rightAligns = null;
        private int _x = 0;
        private int _y = 0;
 
        private int _x = 0;
        private int _y = 0;
 
@@ -26,20 +28,30 @@ public class GuiGridLayout
         */
        public GuiGridLayout(JPanel inPanel)
        {
         */
        public GuiGridLayout(JPanel inPanel)
        {
-               this(inPanel, false);
+               // Default is two columns, with more weight to the right-hand one; first column is right-aligned
+               this(inPanel, null, null);
        }
 
        /**
         * Constructor
         * @param inPanel panel using layout
        }
 
        /**
         * Constructor
         * @param inPanel panel using layout
-        * @param inAllLeft true to align all elements to left
+        * @param inColumnWeights array of column weights
+        * @param inAlignRights array of booleans, true for right alignment, false for left
         */
         */
-       public GuiGridLayout(JPanel inPanel, boolean inAllLeft)
+       public GuiGridLayout(JPanel inPanel, double[] inColumnWeights, boolean[] inAlignRights)
        {
                _panel = inPanel;
        {
                _panel = inPanel;
-               _allLeft = inAllLeft;
                _layout = new GridBagLayout();
                _constraints = new GridBagConstraints();
                _layout = new GridBagLayout();
                _constraints = new GridBagConstraints();
+               _colWeights = inColumnWeights;
+               _rightAligns = inAlignRights;
+               if (_colWeights == null || _rightAligns == null || _colWeights.length != _rightAligns.length
+                       || _colWeights.length < 2)
+               {
+                       _colWeights = new double[] {0.5, 1.0};
+                       _rightAligns = new boolean[] {true, false};
+               }
+               _numColumns = _colWeights.length;
                _constraints.weightx = 1.0;
                _constraints.weighty = 0.0;
                _constraints.ipadx = 10;
                _constraints.weightx = 1.0;
                _constraints.weighty = 0.0;
                _constraints.ipadx = 10;
@@ -57,17 +69,25 @@ public class GuiGridLayout
        {
                _constraints.gridx = _x;
                _constraints.gridy = _y;
        {
                _constraints.gridx = _x;
                _constraints.gridy = _y;
-               _constraints.weightx = (_x==0?0.5:1.0);
+               _constraints.weightx = _colWeights[_x];
                // set anchor
                // set anchor
-               _constraints.anchor = ((_x == 0 && !_allLeft)?GridBagConstraints.LINE_END:GridBagConstraints.LINE_START);
+               _constraints.anchor = (_rightAligns[_x]?GridBagConstraints.LINE_END:GridBagConstraints.LINE_START);
                _layout.setConstraints(inComponent, _constraints);
                // add to panel
                _panel.add(inComponent);
                // work out next position
                _x++;
                _layout.setConstraints(inComponent, _constraints);
                // add to panel
                _panel.add(inComponent);
                // work out next position
                _x++;
-               if (_x > 1) {
-                       _x = 0;
-                       _y++;
+               if (_x >= _numColumns) {
+                       nextRow();
                }
        }
                }
        }
+
+       /**
+        * Go to the next row of the grid
+        */
+       public void nextRow()
+       {
+               _x = 0;
+               _y++;
+       }
 }
 }
index 72537b4098716d08c2a23a4e61f62465ed3c620f..799e90f76b0c8bc2faf221ec12001fca0b0012b6 100644 (file)
@@ -71,6 +71,13 @@ public abstract class IconManager
        /** Icon for stopping the current audio clip */
        public static final String STOP_AUDIO = "stop_audio.gif";
 
        /** Icon for stopping the current audio clip */
        public static final String STOP_AUDIO = "stop_audio.gif";
 
+       /** Icon for a given entry being valid (green tick) */
+       public static final String ENTRY_VALID = "entry_valid.gif";
+       /** Icon for a given entry being invalid (red cross) */
+       public static final String ENTRY_INVALID = "entry_invalid.gif";
+       /** Icon for a given entry being empty (blank) */
+       public static final String ENTRY_NONE = "entry_none.gif";
+
        /**
         * Get the specified image
         * @param inFilename filename of image (using constants)
        /**
         * Get the specified image
         * @param inFilename filename of image (using constants)
index c5501eee75dd653b3a72685bdd71867e14df7cc4..16a0a66a73d7c84792063a4054dcaec256b78598 100644 (file)
@@ -21,6 +21,7 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.AudioClip;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.AudioClip;
+import tim.prune.data.Field;
 import tim.prune.data.Photo;
 import tim.prune.data.RecentFile;
 import tim.prune.data.RecentFileList;
 import tim.prune.data.Photo;
 import tim.prune.data.RecentFile;
 import tim.prune.data.RecentFileList;
@@ -47,6 +48,7 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _exportGpxItem = null;
        private JMenuItem _exportPovItem = null;
        private JMenuItem _exportSvgItem = null;
        private JMenuItem _exportGpxItem = null;
        private JMenuItem _exportPovItem = null;
        private JMenuItem _exportSvgItem = null;
+       private JMenuItem _exportImageItem = null;
        private JMenu     _recentFileMenu = null;
        private JMenuItem _undoItem = null;
        private JMenuItem _clearUndoItem = null;
        private JMenu     _recentFileMenu = null;
        private JMenuItem _undoItem = null;
        private JMenuItem _clearUndoItem = null;
@@ -85,6 +87,8 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _downloadOsmItem = null;
        private JMenuItem _distanceItem = null;
        private JMenuItem _fullRangeDetailsItem = null;
        private JMenuItem _downloadOsmItem = null;
        private JMenuItem _distanceItem = null;
        private JMenuItem _fullRangeDetailsItem = null;
+       private JMenuItem _estimateTimeItem = null;
+       private JMenuItem _learnEstimationParams = null;
        private JMenuItem _saveExifItem = null;
        private JMenuItem _photoPopupItem = null;
        private JMenuItem _selectNoPhotoItem = null;
        private JMenuItem _saveExifItem = null;
        private JMenuItem _photoPopupItem = null;
        private JMenuItem _selectNoPhotoItem = null;
@@ -217,7 +221,11 @@ public class MenuManager implements DataSubscriber
                // Svg
                _exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT, false);
                fileMenu.add(_exportSvgItem);
                // Svg
                _exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT, false);
                fileMenu.add(_exportSvgItem);
+               // Image
+               _exportImageItem = makeMenuItem(FunctionLibrary.FUNCTION_IMAGEEXPORT, false);
+               fileMenu.add(_exportImageItem);
                fileMenu.addSeparator();
                fileMenu.addSeparator();
+               // Exit
                JMenuItem exitMenuItem = new JMenuItem(I18nManager.getText("menu.file.exit"));
                exitMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                JMenuItem exitMenuItem = new JMenuItem(I18nManager.getText("menu.file.exit"));
                exitMenuItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
@@ -312,6 +320,9 @@ public class MenuManager implements DataSubscriber
                trackMenu.add(searchWikipediaNamesItem);
                _downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
                trackMenu.add(_downloadOsmItem);
                trackMenu.add(searchWikipediaNamesItem);
                _downloadOsmItem = makeMenuItem(FunctionLibrary.FUNCTION_DOWNLOAD_OSM, false);
                trackMenu.add(_downloadOsmItem);
+               trackMenu.addSeparator();
+               _learnEstimationParams = makeMenuItem(FunctionLibrary.FUNCTION_LEARN_ESTIMATION_PARAMS, false);
+               trackMenu.add(_learnEstimationParams);
                menubar.add(trackMenu);
 
                // Range menu
                menubar.add(trackMenu);
 
                // Range menu
@@ -450,7 +461,7 @@ public class MenuManager implements DataSubscriber
                        public void actionPerformed(ActionEvent e) {
                                Config.setConfigBoolean(Config.KEY_SHOW_MAP, _mapCheckbox.isSelected());
                                UpdateMessageBroker.informSubscribers(MAPSERVER_CHANGED);
                        public void actionPerformed(ActionEvent e) {
                                Config.setConfigBoolean(Config.KEY_SHOW_MAP, _mapCheckbox.isSelected());
                                UpdateMessageBroker.informSubscribers(MAPSERVER_CHANGED);
-                       }
+                       }
                });
                viewMenu.add(_mapCheckbox);
                // Turn off the sidebars
                });
                viewMenu.add(_mapCheckbox);
                // Turn off the sidebars
@@ -508,12 +519,16 @@ public class MenuManager implements DataSubscriber
                // Charts
                _chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS, false);
                viewMenu.add(_chartItem);
                // Charts
                _chartItem = makeMenuItem(FunctionLibrary.FUNCTION_CHARTS, false);
                viewMenu.add(_chartItem);
+               viewMenu.addSeparator();
                // Distances
                _distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES, false);
                viewMenu.add(_distanceItem);
                // full range details
                _fullRangeDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_RANGE_DETAILS, false);
                viewMenu.add(_fullRangeDetailsItem);
                // Distances
                _distanceItem = makeMenuItem(FunctionLibrary.FUNCTION_DISTANCES, false);
                viewMenu.add(_distanceItem);
                // full range details
                _fullRangeDetailsItem = makeMenuItem(FunctionLibrary.FUNCTION_FULL_RANGE_DETAILS, false);
                viewMenu.add(_fullRangeDetailsItem);
+               // estimate time
+               _estimateTimeItem = makeMenuItem(FunctionLibrary.FUNCTION_ESTIMATE_TIME, false);
+               viewMenu.add(_estimateTimeItem);
                menubar.add(viewMenu);
 
                // Add photo menu
                menubar.add(viewMenu);
 
                // Add photo menu
@@ -613,8 +628,6 @@ public class MenuManager implements DataSubscriber
                settingsMenu.add(_onlineCheckbox);
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_DISK_CACHE));
                settingsMenu.addSeparator();
                settingsMenu.add(_onlineCheckbox);
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_DISK_CACHE));
                settingsMenu.addSeparator();
-               // Set kmz image size
-               settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_KMZ_IMAGE_SIZE));
                // Set program paths
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
                // Set colours
                // Set program paths
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
                // Set colours
@@ -825,22 +838,25 @@ public class MenuManager implements DataSubscriber
         */
        public void dataUpdated(byte inUpdateType)
        {
         */
        public void dataUpdated(byte inUpdateType)
        {
-               boolean hasData = (_track != null && _track.getNumPoints() > 0);
+               final boolean hasData = _track != null && _track.getNumPoints() > 0;
+               final boolean hasMultiplePoints = hasData && _track.getNumPoints() > 1;
+
                // set functions which require data
                _sendGpsItem.setEnabled(hasData);
                _saveItem.setEnabled(hasData);
                _saveButton.setEnabled(hasData);
                _exportKmlItem.setEnabled(hasData);
                _exportGpxItem.setEnabled(hasData);
                // set functions which require data
                _sendGpsItem.setEnabled(hasData);
                _saveItem.setEnabled(hasData);
                _saveButton.setEnabled(hasData);
                _exportKmlItem.setEnabled(hasData);
                _exportGpxItem.setEnabled(hasData);
-               _exportPovItem.setEnabled(hasData);
-               _exportSvgItem.setEnabled(hasData);
+               _exportPovItem.setEnabled(hasMultiplePoints);
+               _exportSvgItem.setEnabled(hasMultiplePoints);
+               _exportImageItem.setEnabled(hasMultiplePoints);
                _compressItem.setEnabled(hasData);
                _markRectangleItem.setEnabled(hasData);
                _deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
                _rearrangeMenu.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
                _selectAllItem.setEnabled(hasData);
                _selectNoneItem.setEnabled(hasData);
                _compressItem.setEnabled(hasData);
                _markRectangleItem.setEnabled(hasData);
                _deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
                _rearrangeMenu.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
                _selectAllItem.setEnabled(hasData);
                _selectNoneItem.setEnabled(hasData);
-               _show3dItem.setEnabled(hasData);
+               _show3dItem.setEnabled(hasMultiplePoints);
                _chartItem.setEnabled(hasData);
                _browserMapMenu.setEnabled(hasData);
                _distanceItem.setEnabled(hasData);
                _chartItem.setEnabled(hasData);
                _browserMapMenu.setEnabled(hasData);
                _distanceItem.setEnabled(hasData);
@@ -850,6 +866,7 @@ public class MenuManager implements DataSubscriber
                _lookupWikipediaItem.setEnabled(hasData);
                _downloadOsmItem.setEnabled(hasData);
                _findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
                _lookupWikipediaItem.setEnabled(hasData);
                _downloadOsmItem.setEnabled(hasData);
                _findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
+
                // is undo available?
                boolean hasUndo = !_app.getUndoStack().isEmpty();
                _undoItem.setEnabled(hasUndo);
                // is undo available?
                boolean hasUndo = !_app.getUndoStack().isEmpty();
                _undoItem.setEnabled(hasUndo);
@@ -882,7 +899,7 @@ public class MenuManager implements DataSubscriber
                _connectButton.setEnabled(connectAvailable);
                _disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto.getDataPoint() != null);
                _correlatePhotosItem.setEnabled(anyPhotos && hasData);
                _connectButton.setEnabled(connectAvailable);
                _disconnectPhotoItem.setEnabled(hasPhoto && currentPhoto.getDataPoint() != null);
                _correlatePhotosItem.setEnabled(anyPhotos && hasData);
-               _rearrangePhotosItem.setEnabled(anyPhotos && hasData && _track.getNumPoints() > 1);
+               _rearrangePhotosItem.setEnabled(anyPhotos && hasMultiplePoints);
                _removePhotoItem.setEnabled(hasPhoto);
                _rotatePhotoLeft.setEnabled(hasPhoto);
                _rotatePhotoRight.setEnabled(hasPhoto);
                _removePhotoItem.setEnabled(hasPhoto);
                _rotatePhotoLeft.setEnabled(hasPhoto);
                _rotatePhotoRight.setEnabled(hasPhoto);
@@ -909,6 +926,9 @@ public class MenuManager implements DataSubscriber
                _convertNamesToTimesItem.setEnabled(hasRange && _track.hasWaypoints());
                _deleteFieldValuesItem.setEnabled(hasRange);
                _fullRangeDetailsItem.setEnabled(hasRange);
                _convertNamesToTimesItem.setEnabled(hasRange && _track.hasWaypoints());
                _deleteFieldValuesItem.setEnabled(hasRange);
                _fullRangeDetailsItem.setEnabled(hasRange);
+               _estimateTimeItem.setEnabled(hasRange);
+               _learnEstimationParams.setEnabled(hasData && _track.hasTrackPoints() && _track.hasData(Field.TIMESTAMP)
+                       && _track.hasAltitudeData());
                // Is the currently selected point outside the current range?
                boolean canCutAndMove = hasRange && hasPoint &&
                        (_selection.getCurrentPointIndex() < _selection.getStart()
                // Is the currently selected point outside the current range?
                boolean canCutAndMove = hasRange && hasPoint &&
                        (_selection.getCurrentPointIndex() < _selection.getStart()
diff --git a/tim/prune/gui/ProgressDialog.java b/tim/prune/gui/ProgressDialog.java
new file mode 100644 (file)
index 0000000..d09087e
--- /dev/null
@@ -0,0 +1,115 @@
+package tim.prune.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+
+import tim.prune.I18nManager;
+
+/**
+ * Class to show a simple progress dialog
+ * similar to swing's ProgressMonitor but with a few
+ * modifications
+ */
+public class ProgressDialog
+{
+       /** Parent frame */
+       private JFrame _parentFrame = null;
+       /** Key for title text */
+       private String _titleKey = null;
+       /** function dialog */
+       private JDialog _dialog = null;
+       /** Progress bar for function */
+       private JProgressBar _progressBar = null;
+       /** Cancel flag */
+       private boolean _cancelled = false;
+
+
+       /**
+        * Constructor
+        * @param inParentFrame parent frame
+        * @param inNameKey key for title
+        */
+       public ProgressDialog(JFrame inParentFrame, String inNameKey)
+       {
+               _parentFrame = inParentFrame;
+               _titleKey = inNameKey;
+       }
+
+       public void show()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(_titleKey), false);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               _progressBar.setMinimum(0);
+               _progressBar.setMaximum(100);
+               _progressBar.setValue(0);
+               _progressBar.setIndeterminate(true);
+               _cancelled = false;
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Make the dialog components
+        * @return the GUI components for the dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+               dialogPanel.add(new JLabel(I18nManager.getText("confirm.running")), BorderLayout.NORTH);
+               _progressBar = new JProgressBar();
+               _progressBar.setPreferredSize(new Dimension(250, 30));
+               dialogPanel.add(_progressBar, BorderLayout.CENTER);
+               // Cancel button at the bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _cancelled = true;
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+       /** Set the maximum value of the progress bar */
+       public void setMaximum(int inMax) {
+               _progressBar.setMaximum(inMax);
+               _progressBar.setIndeterminate(inMax <= 1);
+       }
+
+       /** Set the current value of the progress bar */
+       public void setValue(int inValue) {
+               _progressBar.setValue(inValue);
+       }
+
+       /** Close the dialog */
+       public void dispose() {
+               _dialog.dispose();
+       }
+
+       /**
+        * @return true if cancel button was pressed
+        */
+       public boolean isCancelled() {
+               return _cancelled;
+       }
+}
diff --git a/tim/prune/gui/StatusIcon.java b/tim/prune/gui/StatusIcon.java
new file mode 100644 (file)
index 0000000..26adb1b
--- /dev/null
@@ -0,0 +1,68 @@
+package tim.prune.gui;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+/**
+ * Little status icon for green tick (valid) or red cross (not valid)
+ */
+public class StatusIcon extends JLabel
+{
+       /** Current status */
+       private Status _currStatus = Status.BLANK;
+
+       private static ImageIcon _blankIcon = IconManager.getImageIcon(IconManager.ENTRY_NONE);
+       private static ImageIcon _validIcon = IconManager.getImageIcon(IconManager.ENTRY_VALID);
+       private static ImageIcon _invalidIcon = IconManager.getImageIcon(IconManager.ENTRY_INVALID);
+
+       /**
+        * Three possible states for icon
+        */
+       private enum Status {
+               BLANK,
+               VALID,
+               INVALID
+       }
+
+       /**
+        * Constructor
+        */
+       public StatusIcon()
+       {
+               super(_blankIcon);
+               _currStatus = Status.BLANK;
+       }
+
+       /**
+        * Set the status to blank
+        */
+       public void setStatusBlank()
+       {
+               if (_currStatus != Status.BLANK) {
+                       setIcon(_blankIcon);
+                       _currStatus = Status.BLANK;
+               }
+       }
+
+       /**
+        * Set the status to valid
+        */
+       public void setStatusValid()
+       {
+               if (_currStatus != Status.VALID) {
+                       setIcon(_validIcon);
+                       _currStatus = Status.VALID;
+               }
+       }
+
+       /**
+        * Set the status to not valid
+        */
+       public void setStatusInvalid()
+       {
+               if (_currStatus != Status.INVALID) {
+                       setIcon(_invalidIcon);
+                       _currStatus = Status.INVALID;
+               }
+       }
+}
index 09f676d7e90caac0c119922b3b0bcf894798c9cc..ba953fc67c02381bf246616c21bbacf19fe5b420 100644 (file)
@@ -57,7 +57,7 @@ public class WholeNumberField extends JTextField
         */
        public WholeNumberField(int inMaxDigits)
        {
         */
        public WholeNumberField(int inMaxDigits)
        {
-               super("0");
+               super(inMaxDigits);
                setDocument(new WholeNumberDocument(inMaxDigits));
        }
 
                setDocument(new WholeNumberDocument(inMaxDigits));
        }
 
diff --git a/tim/prune/gui/WizardLayout.java b/tim/prune/gui/WizardLayout.java
new file mode 100644 (file)
index 0000000..1a5a8f1
--- /dev/null
@@ -0,0 +1,102 @@
+package tim.prune.gui;
+
+import java.awt.CardLayout;
+import java.awt.Component;
+import javax.swing.JPanel;
+
+/**
+ * Layout class enhancing the regular card layout to add the ability to
+ * see which is the current card, how many cards there are, previous / next etc
+ */
+public class WizardLayout extends CardLayout
+{
+       private JPanel _panel = null;
+       private int    _currentCard = 0;
+       private int    _numCards = 0;
+
+       /**
+        * Constructor
+        * @param inPanel panel controlled by this layout
+        */
+       public WizardLayout(JPanel inPanel)
+       {
+               super();
+               _panel = inPanel;
+               _panel.setLayout(this);
+       }
+
+       /**
+        * Add a card to this layout
+        * @param inCard
+        */
+       public void addCard(Component inCard)
+       {
+               _panel.add(inCard, "card" + _numCards);
+               _numCards++;
+       }
+
+       /**
+        * @return current card index (from 0)
+        */
+       public int getCurrentCardIndex() {
+               return _currentCard;
+       }
+
+       /**
+        * Go to the first card
+        */
+       public void showFirstCard()
+       {
+               first(_panel);
+               _currentCard = 0;
+       }
+
+       /**
+        * Go to the next card
+        */
+       public void showNextCard()
+       {
+               if (_currentCard < (_numCards-1))
+               {
+                       next(_panel);
+                       _currentCard++;
+               }
+       }
+
+       /**
+        * Go to the previous card
+        */
+       public void showPreviousCard()
+       {
+               if (_currentCard > 0)
+               {
+                       previous(_panel);
+                       _currentCard--;
+               }
+       }
+
+       /**
+        * @return true if this is the first card
+        */
+       public boolean isFirstCard() {
+               return _currentCard == 0;
+       }
+
+       /**
+        * @return true if this is the last card
+        */
+       public boolean isLastCard() {
+               return _currentCard == (_numCards-1);
+       }
+
+       /**
+        * @param inIndex index (from 0) of the card to show
+        */
+       public void showCard(int inIndex)
+       {
+               if (inIndex >= 0 && inIndex < _numCards) {
+                       show(_panel, "card" + inIndex);
+                       _currentCard = inIndex;
+               }
+       }
+}
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/tim/prune/gui/images/entry_invalid.gif b/tim/prune/gui/images/entry_invalid.gif
new file mode 100644 (file)
index 0000000..8612eaf
Binary files /dev/null and b/tim/prune/gui/images/entry_invalid.gif differ
diff --git a/tim/prune/gui/images/entry_none.gif b/tim/prune/gui/images/entry_none.gif
new file mode 100644 (file)
index 0000000..720eeec
Binary files /dev/null and b/tim/prune/gui/images/entry_none.gif differ
diff --git a/tim/prune/gui/images/entry_valid.gif b/tim/prune/gui/images/entry_valid.gif
new file mode 100644 (file)
index 0000000..9ca8275
Binary files /dev/null and b/tim/prune/gui/images/entry_valid.gif differ
index 96ff22367e89d5137e1710c96e104a2911ff80de..a6d5a8861fb62600b9e006f36b29d7466d9dd456 100644 (file)
@@ -390,25 +390,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                int selectedPoint = _selection.getCurrentPointIndex();
                                if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
                                {
                                int selectedPoint = _selection.getCurrentPointIndex();
                                if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
                                {
-                                       int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
-                                       int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
-                                       int panX = 0;
-                                       int panY = 0;
-                                       if (px < PAN_DISTANCE) {
-                                               panX = px - AUTOPAN_DISTANCE;
-                                       }
-                                       else if (px > (getWidth()-PAN_DISTANCE)) {
-                                               panX = AUTOPAN_DISTANCE + px - getWidth();
-                                       }
-                                       if (py < PAN_DISTANCE) {
-                                               panY = py - AUTOPAN_DISTANCE;
-                                       }
-                                       if (py > (getHeight()-PAN_DISTANCE)) {
-                                               panY = AUTOPAN_DISTANCE + py - getHeight();
-                                       }
-                                       if (panX != 0 || panY != 0) {
-                                               _mapPosition.pan(panX, panY);
-                                       }
+                                       autopanToPoint(selectedPoint);
                                }
                                _prevSelectedPoint = selectedPoint;
                        }
                                }
                                _prevSelectedPoint = selectedPoint;
                        }
@@ -446,7 +428,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                                inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
                                        }
                                        break;
                                                inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
                                        }
                                        break;
-                                       
+
                                case MODE_DRAW_POINTS_CONT:
                                        // draw line to mouse position to show drawing mode
                                        inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
                                case MODE_DRAW_POINTS_CONT:
                                        // draw line to mouse position to show drawing mode
                                        inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
@@ -469,6 +451,45 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                paintChildren(inG);
        }
 
                paintChildren(inG);
        }
 
+       /**
+        * @return true if the currently selected point is visible, false if off-screen or nothing selected
+        */
+       private boolean isCurrentPointVisible()
+       {
+               if (_trackInfo.getCurrentPoint() == null) {return false;}
+               final int selectedPoint = _selection.getCurrentPointIndex();
+               final int xFromCentre = Math.abs(_mapPosition.getXFromCentre(_track.getX(selectedPoint)));
+               if (xFromCentre > (getWidth()/2)) {return false;}
+               final int yFromCentre = Math.abs(_mapPosition.getYFromCentre(_track.getY(selectedPoint)));
+               return yFromCentre < (getHeight()/2);
+       }
+
+       /**
+        * If the specified point isn't visible, pan to it
+        * @param inIndex index of selected point
+        */
+       private void autopanToPoint(int inIndex)
+       {
+               int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inIndex));
+               int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inIndex));
+               int panX = 0;
+               int panY = 0;
+               if (px < PAN_DISTANCE) {
+                       panX = px - AUTOPAN_DISTANCE;
+               }
+               else if (px > (getWidth()-PAN_DISTANCE)) {
+                       panX = AUTOPAN_DISTANCE + px - getWidth();
+               }
+               if (py < PAN_DISTANCE) {
+                       panY = py - AUTOPAN_DISTANCE;
+               }
+               if (py > (getHeight()-PAN_DISTANCE)) {
+                       panY = AUTOPAN_DISTANCE + py - getHeight();
+               }
+               if (panX != 0 || panY != 0) {
+                       _mapPosition.pan(panX, panY);
+               }
+       }
 
        /**
         * Paint the map tiles and the points on to the _mapImage
 
        /**
         * Paint the map tiles and the points on to the _mapImage
@@ -550,8 +571,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                {
                        pointsPainted = paintPoints(g);
                }
                {
                        pointsPainted = paintPoints(g);
                }
-               catch (NullPointerException npe) { // ignore, probably due to data being changed during drawing
-               }
+               catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
+               catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
 
                // free g
                g.dispose();
 
                // free g
                g.dispose();
@@ -658,6 +679,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                int nameHeight = fm.getHeight();
                if (anyWaypoints)
                {
                int nameHeight = fm.getHeight();
                if (anyWaypoints)
                {
+                       int numWaypoints = 0;
                        for (int i=0; i<_track.getNumPoints(); i++)
                        {
                                if (_track.getPoint(i).isWaypoint())
                        for (int i=0; i<_track.getNumPoints(); i++)
                        {
                                if (_track.getPoint(i).isWaypoint())
@@ -668,11 +690,18 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                        {
                                                inG.fillRect(px-3, py-3, 6, 6);
                                                pointsPainted++;
                                        {
                                                inG.fillRect(px-3, py-3, 6, 6);
                                                pointsPainted++;
+                                               numWaypoints++;
                                        }
                                }
                        }
                                        }
                                }
                        }
+                       // Take more care with waypoint names if less than 100 are visible
+                       final int numNameSteps = (numWaypoints > 100 ? 1 : 4);
+                       final int numPointSteps = (numWaypoints > 1000 ? 2 : 1);
+
                        // Loop over points again, now draw names for waypoints
                        // Loop over points again, now draw names for waypoints
-                       for (int i=0; i<_track.getNumPoints(); i++)
+                       int[] nameXs = {0, 0, 0, 0};
+                       int[] nameYs = {0, 0, 0, 0};
+                       for (int i=0; i<_track.getNumPoints(); i += numPointSteps)
                        {
                                if (_track.getPoint(i).isWaypoint())
                                {
                        {
                                if (_track.getPoint(i).isWaypoint())
                                {
@@ -685,19 +714,21 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                                int nameWidth = fm.stringWidth(waypointName);
                                                boolean drawnName = false;
                                                // Make arrays for coordinates right left up down
                                                int nameWidth = fm.stringWidth(waypointName);
                                                boolean drawnName = false;
                                                // Make arrays for coordinates right left up down
-                                               int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
-                                               int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
-                                               for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+                                               nameXs[0] = px + 2; nameXs[1] = px - nameWidth - 2;
+                                               nameXs[2] = nameXs[3] = px - nameWidth/2;
+                                               nameYs[0] = nameYs[1] = py + (nameHeight/2);
+                                               nameYs[2] = py - 2; nameYs[3] = py + nameHeight + 2;
+                                               for (int extraSpace = 0; extraSpace < numNameSteps && !drawnName; extraSpace++)
                                                {
                                                        // Shift arrays for coordinates right left up down
                                                {
                                                        // Shift arrays for coordinates right left up down
-                                                       nameXs[0] += 2; nameXs[1] -= 2;
-                                                       nameYs[2] -= 2; nameYs[3] += 2;
+                                                       nameXs[0] += 3; nameXs[1] -= 3;
+                                                       nameYs[2] -= 3; nameYs[3] += 3;
                                                        // Check each direction in turn right left up down
                                                        for (int a=0; a<4; a++)
                                                        {
                                                                if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < winWidth
                                                                        && nameYs[a] < winHeight && (nameYs[a] - nameHeight) > 0
                                                        // Check each direction in turn right left up down
                                                        for (int a=0; a<4; a++)
                                                        {
                                                                if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < winWidth
                                                                        && nameYs[a] < winHeight && (nameYs[a] - nameHeight) > 0
-                                                                       && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
+                                                                       && !MapUtils.overlapsPoints(_mapImage, nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
                                                                {
                                                                        // Found a rectangle to fit - draw name here and quit
                                                                        inG.drawString(waypointName, nameXs[a], nameYs[a]);
                                                                {
                                                                        // Found a rectangle to fit - draw name here and quit
                                                                        inG.drawString(waypointName, nameXs[a], nameYs[a]);
@@ -813,50 +844,6 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                }
        }
 
                }
        }
 
-       /**
-        * Tests whether there are any dark pixels within the specified x,y rectangle
-        * @param inX left X coordinate
-        * @param inY bottom Y coordinate
-        * @param inWidth width of rectangle
-        * @param inHeight height of rectangle
-        * @param inTextColour colour of text
-        * @return true if the rectangle overlaps stuff too close to the given colour
-        */
-       private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight, Color inTextColour)
-       {
-               // each of the colour channels must be further away than this to count as empty
-               final int BRIGHTNESS_LIMIT = 80;
-               final int textRGB = inTextColour.getRGB();
-               final int textLow = textRGB & 255;
-               final int textMid = (textRGB >> 8) & 255;
-               final int textHigh = (textRGB >> 16) & 255;
-               try
-               {
-                       // loop over x coordinate of rectangle
-                       for (int x=0; x<inWidth; x++)
-                       {
-                               // loop over y coordinate of rectangle
-                               for (int y=0; y<inHeight; y++)
-                               {
-                                       int pixelColor = _mapImage.getRGB(inX + x, inY - y);
-                                       // split into four components rgba
-                                       int pixLow = pixelColor & 255;
-                                       int pixMid = (pixelColor >> 8) & 255;
-                                       int pixHigh = (pixelColor >> 16) & 255;
-                                       //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
-                                       // If colours are too close in any channel then it's an overlap
-                                       if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
-                                               Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
-                                               Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
-                               }
-                       }
-               }
-               catch (NullPointerException e) {
-                       // ignore null pointers, just return false
-               }
-               return false;
-       }
-
        /**
         * Make a semi-transparent colour for drawing with
         * @param inColour base colour (fully opaque)
        /**
         * Make a semi-transparent colour for drawing with
         * @param inColour base colour (fully opaque)
@@ -906,7 +893,12 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
         */
        public void zoomIn()
        {
         */
        public void zoomIn()
        {
+               // See if selected point is currently visible, if so (and autopan on) then autopan after zoom to keep it visible
+               boolean wasVisible = _autopanCheckBox.isSelected() && isCurrentPointVisible();
                _mapPosition.zoomIn();
                _mapPosition.zoomIn();
+               if (wasVisible && !isCurrentPointVisible()) {
+                       autopanToPoint(_selection.getCurrentPointIndex());
+               }
                _recalculate = true;
                repaint();
        }
                _recalculate = true;
                repaint();
        }
index 78206a3956959f6126ed1568a29893a61ce76e36..49d94676f77c70cc137fa1d204a11dffcd4eea27 100644 (file)
@@ -1,7 +1,10 @@
 package tim.prune.gui.map;
 
 package tim.prune.gui.map;
 
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+
 /**
 /**
- * Class to manage coordinate conversions for maps
+ * Class to manage coordinate conversions and other stuff for maps
  */
 public abstract class MapUtils
 {
  */
 public abstract class MapUtils
 {
@@ -49,4 +52,50 @@ public abstract class MapUtils
                double n = Math.PI * (1 - 2 * inY);
                return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
        }
                double n = Math.PI * (1 - 2 * inY);
                return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
        }
+
+       /**
+        * Tests whether there are any dark pixels in the image within the specified x,y rectangle
+        * @param inImage image to test
+        * @param inX left X coordinate
+        * @param inY bottom Y coordinate
+        * @param inWidth width of rectangle
+        * @param inHeight height of rectangle
+        * @param inTextColour colour of text
+        * @return true if the rectangle overlaps stuff too close to the given colour
+        */
+       public static boolean overlapsPoints(BufferedImage inImage, int inX, int inY,
+               int inWidth, int inHeight, Color inTextColour)
+       {
+               // each of the colour channels must be further away than this to count as empty
+               final int BRIGHTNESS_LIMIT = 80;
+               final int textRGB = inTextColour.getRGB();
+               final int textLow = textRGB & 255;
+               final int textMid = (textRGB >> 8) & 255;
+               final int textHigh = (textRGB >> 16) & 255;
+               try
+               {
+                       // loop over x coordinate of rectangle
+                       for (int x=0; x<inWidth; x++)
+                       {
+                               // loop over y coordinate of rectangle
+                               for (int y=0; y<inHeight; y++)
+                               {
+                                       int pixelColor = inImage.getRGB(inX + x, inY - y);
+                                       // split into four components rgba
+                                       int pixLow = pixelColor & 255;
+                                       int pixMid = (pixelColor >> 8) & 255;
+                                       int pixHigh = (pixelColor >> 16) & 255;
+                                       //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
+                                       // If colours are too close in any channel then it's an overlap
+                                       if (Math.abs(pixLow-textLow) < BRIGHTNESS_LIMIT ||
+                                               Math.abs(pixMid-textMid) < BRIGHTNESS_LIMIT ||
+                                               Math.abs(pixHigh-textHigh) < BRIGHTNESS_LIMIT) {return true;}
+                               }
+                       }
+               }
+               catch (NullPointerException e) {
+                       // ignore null pointers, just return false
+               }
+               return false;
+       }
 }
 }
index 9c19862166b2421b4cdaab9d102a75b5eb26dad6..9600b1674ade17a296a9e43f9a1dc4039dfcffe1 100644 (file)
@@ -30,9 +30,9 @@ public class AltitudeData extends ProfileData
                final double multFactor = _unitSet.getAltitudeUnit().getMultFactorFromStd();
                if (_track != null)
                {
                final double multFactor = _unitSet.getAltitudeUnit().getMultFactorFromStd();
                if (_track != null)
                {
-                       for (int i=0; i<_track.getNumPoints(); i++)
+                       try
                        {
                        {
-                               try
+                               for (int i=0; i<_track.getNumPoints(); i++)
                                {
                                        DataPoint point = _track.getPoint(i);
                                        if (point != null && point.hasAltitude())
                                {
                                        DataPoint point = _track.getPoint(i);
                                        if (point != null && point.hasAltitude())
@@ -43,15 +43,16 @@ public class AltitudeData extends ProfileData
                                                if (value < _minValue || !_hasData) {_minValue = value;}
                                                if (value > _maxValue || !_hasData) {_maxValue = value;}
 
                                                if (value < _minValue || !_hasData) {_minValue = value;}
                                                if (value > _maxValue || !_hasData) {_maxValue = value;}
 
-                                               _hasData = true;
+                                               // if all values are zero then that's no data
+                                               _hasData = _hasData || (point.getAltitude().getValue() != 0);
                                                _pointHasData[i] = true;
                                        }
                                        else _pointHasData[i] = false;
                                }
                                                _pointHasData[i] = true;
                                        }
                                        else _pointHasData[i] = false;
                                }
-                               catch (ArrayIndexOutOfBoundsException obe)
-                               {} // must be due to the track size changing during calculation
-                                  // assume that a redraw will be triggered
                        }
                        }
+                       catch (ArrayIndexOutOfBoundsException obe)
+                       {} // must be due to the track size changing during calculation
+                          // assume that a redraw will be triggered
                }
        }
 
                }
        }
 
diff --git a/tim/prune/gui/profile/ArbitraryData.java b/tim/prune/gui/profile/ArbitraryData.java
new file mode 100644 (file)
index 0000000..cf974a5
--- /dev/null
@@ -0,0 +1,79 @@
+package tim.prune.gui.profile;
+
+import tim.prune.data.Field;
+import tim.prune.data.Track;
+import tim.prune.data.UnitSet;
+
+/**
+ * Class to provide a source of values for the profile chart
+ * using any arbitary (non-built-in) field, units unknown
+ */
+public class ArbitraryData extends ProfileData
+{
+       /** Field to use */
+       private Field _field = null;
+
+       /**
+        * Constructor
+        * @param inTrack track object
+        * @param inField field to use
+        */
+       public ArbitraryData(Track inTrack, Field inField)
+       {
+               super(inTrack);
+               _field = inField;
+       }
+
+       /**
+        * Get the data and populate the instance arrays
+        */
+       public void init(UnitSet inUnitSet)
+       {
+               setUnitSet(inUnitSet);
+               initArrays();
+               _hasData = false;
+               _minValue = _maxValue = 0.0;
+               if (_track != null)
+               {
+                       for (int i=0; i<_track.getNumPoints(); i++)
+                       {
+                               // Get the value of the given field
+                               boolean hasValue = false;
+                               String value = _track.getPoint(i).getFieldValue(_field);
+                               try
+                               {
+                                       double dValue = Double.parseDouble(value);
+                                       _pointValues[i] = dValue;
+                                       if (dValue < _minValue || _minValue == 0.0) {_minValue = dValue;}
+                                       if (dValue > _maxValue) {_maxValue = dValue;}
+                                       hasValue = true;
+                                       _hasData = true;
+                               }
+                               catch (Exception e) {} // ignore nulls and non-numbers
+                               _pointHasData[i] = hasValue;
+                       }
+               }
+       }
+
+       /**
+        * @return name of field
+        */
+       public String getLabel()
+       {
+               return _field.getName();
+       }
+
+       /**
+        * @return the field object
+        */
+       public Field getField() {
+               return _field;
+       }
+
+       /**
+        * @return key for message when no values present
+        */
+       public String getNoDataKey() {
+               return "display.novalues";
+       }
+}
index 770a211ea777535d900bb43bfed109c047b771a5..732669a956dcd48bf719848e5223b2b77bf989fe 100644 (file)
@@ -15,6 +15,8 @@ import javax.swing.JPopupMenu;
 import tim.prune.I18nManager;
 import tim.prune.config.ColourScheme;
 import tim.prune.config.Config;
 import tim.prune.I18nManager;
 import tim.prune.config.ColourScheme;
 import tim.prune.config.Config;
+import tim.prune.data.Field;
+import tim.prune.data.FieldList;
 import tim.prune.data.TrackInfo;
 import tim.prune.gui.GenericDisplay;
 
 import tim.prune.data.TrackInfo;
 import tim.prune.gui.GenericDisplay;
 
@@ -23,6 +25,17 @@ import tim.prune.gui.GenericDisplay;
  */
 public class ProfileChart extends GenericDisplay implements MouseListener
 {
  */
 public class ProfileChart extends GenericDisplay implements MouseListener
 {
+       /** Inner class to handle popup menu clicks */
+       class MenuClicker implements ActionListener
+       {
+               private Field _field = null;
+               MenuClicker(Field inField) {_field = inField;}
+               /** React to menu click by changing the field */
+               public void actionPerformed(ActionEvent arg0) {
+                       changeView(_field);
+               }
+       }
+
        /** Current scale factor in x direction*/
        private double _xScaleFactor = 0.0;
        /** Data to show on chart */
        /** Current scale factor in x direction*/
        private double _xScaleFactor = 0.0;
        /** Data to show on chart */
@@ -40,8 +53,6 @@ public class ProfileChart extends GenericDisplay implements MouseListener
        private static final Dimension MINIMUM_SIZE = new Dimension(200, 110);
        /** Colour to use for text if no data found */
        private static final Color COLOR_NODATA_TEXT = Color.GRAY;
        private static final Dimension MINIMUM_SIZE = new Dimension(200, 110);
        /** Colour to use for text if no data found */
        private static final Color COLOR_NODATA_TEXT = Color.GRAY;
-       /** Chart type */
-       private static enum ChartType {ALTITUDE, SPEED, VERT_SPEED};
 
 
        /**
 
 
        /**
@@ -255,23 +266,38 @@ public class ProfileChart extends GenericDisplay implements MouseListener
                altItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                altItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               changeView(ChartType.ALTITUDE);
+                               changeView(Field.ALTITUDE);
                        }});
                _popup.add(altItem);
                JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
                speedItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                        }});
                _popup.add(altItem);
                JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
                speedItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               changeView(ChartType.SPEED);
+                               changeView(Field.SPEED);
                        }});
                _popup.add(speedItem);
                JMenuItem vertSpeedItem = new JMenuItem(I18nManager.getText("fieldname.verticalspeed"));
                vertSpeedItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                        }});
                _popup.add(speedItem);
                JMenuItem vertSpeedItem = new JMenuItem(I18nManager.getText("fieldname.verticalspeed"));
                vertSpeedItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               changeView(ChartType.VERT_SPEED);
+                               changeView(Field.VERTICAL_SPEED);
                        }});
                _popup.add(vertSpeedItem);
                        }});
                _popup.add(vertSpeedItem);
+               // Go through track's master field list, see if any other fields to list
+               boolean addSeparator = true;
+               FieldList fields = _track.getFieldList();
+               for (int i=0; i<fields.getNumFields(); i++)
+               {
+                       Field field = fields.getField(i);
+                       if (!field.isBuiltIn())
+                       {
+                               if (addSeparator) {_popup.addSeparator();}
+                               addSeparator = false;
+                               JMenuItem item = new JMenuItem(field.getName());
+                               item.addActionListener(new MenuClicker(field));
+                               _popup.add(item);
+                       }
+               }
        }
 
        /**
        }
 
        /**
@@ -309,6 +335,10 @@ public class ProfileChart extends GenericDisplay implements MouseListener
                if (inUpdateType != SELECTION_CHANGED) {
                        _data.init(Config.getUnitSet());
                }
                if (inUpdateType != SELECTION_CHANGED) {
                        _data.init(Config.getUnitSet());
                }
+               // Update the menu if necessary
+               if ((inUpdateType & DATA_ADDED_OR_REMOVED) > 0) {
+                       makePopup();
+               }
                repaint();
        }
 
                repaint();
        }
 
@@ -346,19 +376,31 @@ public class ProfileChart extends GenericDisplay implements MouseListener
 
        /**
         * Called by clicking on popup menu to change the view
 
        /**
         * Called by clicking on popup menu to change the view
-        * @param inType selected chart type
+        * @param inField field to show
         */
         */
-       private void changeView(ChartType inType)
+       private void changeView(Field inField)
        {
        {
-               if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
+               if (inField == Field.ALTITUDE)
                {
                {
-                       _data = new AltitudeData(_track);
+                       if (!(_data instanceof AltitudeData)) {
+                               _data = new AltitudeData(_track);
+                       }
                }
                }
-               else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
-                       _data = new SpeedData(_track);
+               else if (inField == Field.SPEED) {
+                       if (!(_data instanceof SpeedData)) {
+                               _data = new SpeedData(_track);
+                       }
                }
                }
-               else if (inType == ChartType.VERT_SPEED && !(_data instanceof VerticalSpeedData)) {
-                       _data = new VerticalSpeedData(_track);
+               else if (inField == Field.VERTICAL_SPEED) {
+                       if (!(_data instanceof VerticalSpeedData)) {
+                               _data = new VerticalSpeedData(_track);
+                       }
+               }
+               else
+               {
+                       if (!(_data instanceof ArbitraryData) || ((ArbitraryData)_data).getField() != inField) {
+                               _data = new ArbitraryData(_track, inField);
+                       }
                }
                _data.init(Config.getUnitSet());
                repaint();
                }
                _data.init(Config.getUnitSet());
                repaint();
index 2d0e54afef2a9dd2de1f4ad3e887e20841b2fa28..09d72deab611db074b3f26b6710bbbd3a319781c 100644 (file)
@@ -95,7 +95,6 @@ function.show3d=3D Vertoon
 function.distances=Afstande
 function.fullrangedetails=Vol reeks besonderhede
 function.setmapbg=Stel Kaart agtergrond
 function.distances=Afstande
 function.fullrangedetails=Vol reeks besonderhede
 function.setmapbg=Stel Kaart agtergrond
-function.setkmzimagesize=Stel KMZ beeld groote
 function.setpaths=Stel program paaie
 function.getgpsies=Kry GPS spore
 function.lookupsrtm=Kry hoogtes vanaf SRTM
 function.setpaths=Stel program paaie
 function.getgpsies=Kry GPS spore
 function.lookupsrtm=Kry hoogtes vanaf SRTM
@@ -188,7 +187,7 @@ dialog.exportpov.cameraz=Kamera Z
 dialog.exportpov.modelstyle=Model styl
 dialog.exportpov.ballsandsticks=Balle en stokkies
 dialog.exportpov.tubesandwalls=Buise en mure
 dialog.exportpov.modelstyle=Model styl
 dialog.exportpov.ballsandsticks=Balle en stokkies
 dialog.exportpov.tubesandwalls=Buise en mure
-dialog.exportpov.warningtracksize=Hierdie spoor het 'n groot aantal punte, wat Java3D miskien nie kan vertoon.\nIs jy seker jy wil voortgaan?
+dialog.3d.warningtracksize=Hierdie spoor het 'n groot aantal punte, wat Java3D miskien nie kan vertoon.\nIs jy seker jy wil voortgaan?
 dialog.exportsvg.text=Selekteer die parameters vir die SVG uitvoer
 dialog.exportsvg.phi=Azimuth hoek \u03d5
 dialog.exportsvg.theta=Opstandings angle \u03b8
 dialog.exportsvg.text=Selekteer die parameters vir die SVG uitvoer
 dialog.exportsvg.phi=Azimuth hoek \u03d5
 dialog.exportsvg.theta=Opstandings angle \u03b8
index 6c5a9dbc6578cf2d78d74b7b654efae96d6e4def..b922f3bcbbb8b85ff3e29da18a14b3286dc27db7 100644 (file)
@@ -7,7 +7,7 @@ menu.file.addphotos=P\u0159idat fotografie
 menu.file.recentfiles=Naposledy otev\u0159en\u00e9
 menu.file.save=Ulo\u017eit jako text
 menu.file.exit=Konec
 menu.file.recentfiles=Naposledy otev\u0159en\u00e9
 menu.file.save=Ulo\u017eit jako text
 menu.file.exit=Konec
-menu.track=Trasa
+menu.track=Stopa
 menu.track.undo=Undo
 menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
 menu.track.markrectangle=Ozna\u010dit body v obd\u00e9ln\u00edku
 menu.track.undo=Undo
 menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
 menu.track.markrectangle=Ozna\u010dit body v obd\u00e9ln\u00edku
@@ -15,7 +15,7 @@ menu.track.deletemarked=Smazat ozna\u010den\u00e9 body
 menu.track.rearrange=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
 menu.track.rearrange.start=V\u0161e na po\u010d\u00e1tek
 menu.track.rearrange.end=V\u0161e na konec
 menu.track.rearrange=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
 menu.track.rearrange.start=V\u0161e na po\u010d\u00e1tek
 menu.track.rearrange.end=V\u0161e na konec
-menu.track.rearrange.nearest=Zarovnat body na trasu
+menu.track.rearrange.nearest=Zarovnat body na stopu
 menu.range=Rozmez\u00ed
 menu.range.all=Vybrat v\u0161e
 menu.range.none=Zru\u0161it v\u00fdb\u011br
 menu.range=Rozmez\u00ed
 menu.range.all=Vybrat v\u0161e
 menu.range.none=Zru\u0161it v\u00fdb\u011br
@@ -23,7 +23,7 @@ menu.range.start=Nastavit za\u010d\u00e1tek rozmez\u00ed
 menu.range.end=Nastavit konec rozmez\u00ed
 menu.range.average=St\u0159ed z v\u00fdb\u011bru
 menu.range.reverse=Obr\u00e1tit rozmez\u00ed
 menu.range.end=Nastavit konec rozmez\u00ed
 menu.range.average=St\u0159ed z v\u00fdb\u011bru
 menu.range.reverse=Obr\u00e1tit rozmez\u00ed
-menu.range.mergetracksegments=Slou\u010dit \u010d\u00e1sti trasy
+menu.range.mergetracksegments=Slou\u010dit \u010d\u00e1sti stopy
 menu.range.cutandmove=P\u0159en\u00e9st v\u00fdb\u011br
 menu.point=Bod
 menu.point.editpoint=Upravit bod
 menu.range.cutandmove=P\u0159en\u00e9st v\u00fdb\u011br
 menu.point=Bod
 menu.point.editpoint=Upravit bod
@@ -49,7 +49,7 @@ menu.map.zoomout=Odd\u00e1lit
 menu.map.zoomfull=\u00dapln\u011b odd\u00e1lit
 menu.map.newpoint=Vytvo\u0159it nov\u00fd bod
 menu.map.drawpoints=Vytvo\u0159it n\u011bkolik bod\u016f
 menu.map.zoomfull=\u00dapln\u011b odd\u00e1lit
 menu.map.newpoint=Vytvo\u0159it nov\u00fd bod
 menu.map.drawpoints=Vytvo\u0159it n\u011bkolik bod\u016f
-menu.map.connect=Propojit body trasy
+menu.map.connect=Propojit body stopy
 menu.map.autopan=Automatika zorn\u00e9ho pole
 menu.map.showmap=Zobrazit mapu
 menu.map.showscalebar=Zobrazit m\u011b\u0159\u00edtko
 menu.map.autopan=Automatika zorn\u00e9ho pole
 menu.map.showmap=Zobrazit mapu
 menu.map.showscalebar=Zobrazit m\u011b\u0159\u00edtko
@@ -84,10 +84,11 @@ function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
+function.exportimage=Export obrazu mapy
 function.editwaypointname=Nastavit n\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
 function.editwaypointname=Nastavit n\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
-function.compress=Komprimovat trasu
+function.compress=Komprimovat stopu
 function.deleterange=Smazat rozmez\u00ed
 function.deleterange=Smazat rozmez\u00ed
-function.croptrack=O\u0159\u00edznout trasu
+function.croptrack=O\u0159\u00edznout stopu
 function.interpolate=Interpolovat body
 function.addtimeoffset=P\u0159idat \u010dasov\u00fd posun
 function.addaltitudeoffset=P\u0159idat v\u00fd\u0161kov\u00fd posun
 function.interpolate=Interpolovat body
 function.addtimeoffset=P\u0159idat \u010dasov\u00fd posun
 function.addaltitudeoffset=P\u0159idat v\u00fd\u0161kov\u00fd posun
@@ -99,11 +100,12 @@ function.charts=Grafy
 function.show3d=Trojrozm\u011brn\u011b
 function.distances=Vzd\u00e1lenosti
 function.fullrangedetails=Detaily rozmez\u00ed
 function.show3d=Trojrozm\u011brn\u011b
 function.distances=Vzd\u00e1lenosti
 function.fullrangedetails=Detaily rozmez\u00ed
+function.estimatetime=Odhad \u010dasu
+function.learnestimationparams=Anal\u00fdza stopy pro odhad \u010dasu
 function.setmapbg=Nastavit pozad\u00ed
 function.setmapbg=Nastavit pozad\u00ed
-function.setkmzimagesize=Nastavit velikost exportu KMZ
 function.setpaths=Nastavit cestu k program\u016fm
 function.setpaths=Nastavit cestu k program\u016fm
-function.getgpsies=St\u00e1hnout trasy z Gpsies
-function.uploadgpsies=Nahr\u00e1t trasu na Gpsies
+function.getgpsies=St\u00e1hnout stopy z Gpsies
+function.uploadgpsies=Nahr\u00e1t stopu na Gpsies
 function.lookupsrtm=Na\u010d\u00edst nadm. v\u00fd\u0161ku ze SRTM
 function.getwikipedia=Hledat na Wikipedii podle vzd\u00e1lenosti
 function.searchwikipedianames=Hledat na Wikipedii podle jm\u00e9na
 function.lookupsrtm=Na\u010d\u00edst nadm. v\u00fd\u0161ku ze SRTM
 function.getwikipedia=Hledat na Wikipedii podle vzd\u00e1lenosti
 function.searchwikipedianames=Hledat na Wikipedii podle jm\u00e9na
@@ -159,8 +161,12 @@ dialog.openoptions.deliminfo.records=z\u00e1znam\u016f, s
 dialog.openoptions.deliminfo.fields=poli
 dialog.openoptions.deliminfo.norecords=\u017d\u00e1dn\u00e9 z\u00e1znamy
 dialog.openoptions.altitudeunits=Jednotky v\u00fd\u0161ky
 dialog.openoptions.deliminfo.fields=poli
 dialog.openoptions.deliminfo.norecords=\u017d\u00e1dn\u00e9 z\u00e1znamy
 dialog.openoptions.altitudeunits=Jednotky v\u00fd\u0161ky
-dialog.open.contentsdoubled=Tento soubor obsahuje dv\u011b kopie ka\u017ed\u00e9ho bodu,\nv\u017edy jednou jako body trasy a jednou jako v\u00fdzna\u010dn\u00e9 body.
-dialog.selecttracks.intro=Vyberte trasu nebo trasy k na\u010dten\u00ed
+dialog.openoptions.speedunits=Jednotky rychlosti
+dialog.openoptions.vertspeedunits=Jednotky vertik\u00e1ln\u00ed rychlosti
+dialog.openoptions.vspeed.positiveup=Kladn\u00e1 rychlost znamen\u00e1 stoup\u00e1n\u00ed
+dialog.openoptions.vspeed.positivedown=Kladn\u00e1 rychlost znamen\u00e1 kles\u00e1n\u00ed
+dialog.open.contentsdoubled=Tento soubor obsahuje dv\u011b kopie ka\u017ed\u00e9ho bodu,\nv\u017edy jednou jako body stopy a jednou jako v\u00fdzna\u010dn\u00e9 body.
+dialog.selecttracks.intro=Vyberte stopu nebo stopy k na\u010dten\u00ed
 dialog.selecttracks.noname=Bez n\u00e1zvu
 dialog.jpegload.subdirectories=V\u010detn\u011b podadres\u00e1\u0159\u016f
 dialog.jpegload.loadjpegswithoutcoords=V\u010detn\u011b fotografi\u00ed bez sou\u0159adnic
 dialog.selecttracks.noname=Bez n\u00e1zvu
 dialog.jpegload.subdirectories=V\u010detn\u011b podadres\u00e1\u0159\u016f
 dialog.jpegload.loadjpegswithoutcoords=V\u010detn\u011b fotografi\u00ed bez sou\u0159adnic
@@ -171,11 +177,35 @@ dialog.gpsload.nogpsbabel=Nenalezen program gpsbabel. Pokra\u010dovat?
 dialog.gpsload.device=Ozna\u010den\u00ed za\u0159\u00edzen\u00ed
 dialog.gpsload.format=Form\u00e1t
 dialog.gpsload.getwaypoints=Na\u010d\u00edst v\u00fdzna\u010dn\u00e9 body
 dialog.gpsload.device=Ozna\u010den\u00ed za\u0159\u00edzen\u00ed
 dialog.gpsload.format=Form\u00e1t
 dialog.gpsload.getwaypoints=Na\u010d\u00edst v\u00fdzna\u010dn\u00e9 body
-dialog.gpsload.gettracks=Na\u010d\u00edst trasy
+dialog.gpsload.gettracks=Na\u010d\u00edst stopy
 dialog.gpsload.save=Ulo\u017eit do souboru
 dialog.gpssend.sendwaypoints=Poslat bod
 dialog.gpsload.save=Ulo\u017eit do souboru
 dialog.gpssend.sendwaypoints=Poslat bod
-dialog.gpssend.sendtracks=Poslat trasy
-dialog.gpssend.trackname=N\u00e1zev trasy
+dialog.gpssend.sendtracks=Poslat stopy
+dialog.gpssend.trackname=N\u00e1zev stopy
+dialog.gpsbabel.filters=Filtry
+dialog.addfilter.title=P\u0159idat filtr
+dialog.gpsbabel.filter.discard=Vynech\u00e1n\u00ed
+dialog.gpsbabel.filter.simplify=Zjednodu\u0161en\u00ed
+dialog.gpsbabel.filter.distance=Vzd\u00e1lenost
+dialog.gpsbabel.filter.interpolate=Interpolace
+dialog.gpsbabel.filter.discard.intro=Body vynechat, kdy\u017e
+dialog.gpsbabel.filter.discard.hdop=hdop >
+dialog.gpsbabel.filter.discard.vdop=vdop >
+dialog.gpsbabel.filter.discard.numsats=po\u010det satelit\u016f <
+dialog.gpsbabel.filter.discard.nofix=bod nem\u00e1 fix
+dialog.gpsbabel.filter.discard.unknownfix=fix bodu je nezn\u00e1m\u00fd
+dialog.gpsbabel.filter.simplify.intro=Odstra\u0148ovat body dokud
+dialog.gpsbabel.filter.simplify.maxpoints=po\u010det bod\u016f <
+dialog.gpsbabel.filter.simplify.maxerror=nebo max. odchylka <
+dialog.gpsbabel.filter.simplify.crosstrack=nap\u0159\u00ed\u010d stopami
+dialog.gpsbabel.filter.simplify.length=rozd\u00edl d\u00e9lek
+dialog.gpsbabel.filter.simplify.relative=relativn\u011b k hdop
+dialog.gpsbabel.filter.distance.intro=Smazat body
+dialog.gpsbabel.filter.distance.distance=pokud vzd\u00e1lenost <
+dialog.gpsbabel.filter.distance.time=a \u010dasov\u00fd rozd\u00edl <
+dialog.gpsbabel.filter.interpolate.intro=P\u0159idat dal\u0161\u00ed body mezi
+dialog.gpsbabel.filter.interpolate.distance=pokud vzd\u00e1lenost >
+dialog.gpsbabel.filter.interpolate.time=nebo \u010dasov\u00fd rozd\u00edl >
 dialog.saveoptions.title=Ulo\u017eit soubor
 dialog.save.fieldstosave=Ulo\u017eit pole
 dialog.save.table.field=Pole
 dialog.saveoptions.title=Ulo\u017eit soubor
 dialog.save.fieldstosave=Ulo\u017eit pole
 dialog.save.table.field=Pole
@@ -192,7 +222,10 @@ dialog.exportkml.text=Nadpis dat
 dialog.exportkml.altitude=V\u00fd\u0161ka nad hladinou mo\u0159e (pro letectv\u00ed)
 dialog.exportkml.kmz=Komprimovat do souboru kmz
 dialog.exportkml.exportimages=Vlo\u017eit n\u00e1hledy fotografi\u00ed
 dialog.exportkml.altitude=V\u00fd\u0161ka nad hladinou mo\u0159e (pro letectv\u00ed)
 dialog.exportkml.kmz=Komprimovat do souboru kmz
 dialog.exportkml.exportimages=Vlo\u017eit n\u00e1hledy fotografi\u00ed
-dialog.exportkml.trackcolour=Barva trasy
+dialog.exportkml.imagesize=Velikost obr\u00e1zku
+dialog.exportkml.trackcolour=Barva stopy
+dialog.exportkml.standardkml=Standardn\u00ed KML
+dialog.exportkml.extendedkml=Roz\u0161\u00ed\u0159en\u00e9 KML s \u010dasov\u00fdmi zna\u010dkami
 dialog.exportgpx.name=N\u00e1zev
 dialog.exportgpx.desc=Popis
 dialog.exportgpx.includetimestamps=Ulo\u017eit \u010dasov\u00e9 zna\u010dky
 dialog.exportgpx.name=N\u00e1zev
 dialog.exportgpx.desc=Popis
 dialog.exportgpx.includetimestamps=Ulo\u017eit \u010dasov\u00e9 zna\u010dky
@@ -208,23 +241,35 @@ dialog.exportpov.cameraz=Kamera Z
 dialog.exportpov.modelstyle=Model
 dialog.exportpov.ballsandsticks=Koule a ty\u010dky
 dialog.exportpov.tubesandwalls=Roury a st\u011bny
 dialog.exportpov.modelstyle=Model
 dialog.exportpov.ballsandsticks=Koule a ty\u010dky
 dialog.exportpov.tubesandwalls=Roury a st\u011bny
-dialog.exportpov.warningtracksize=Tato trasa sest\u00e1v\u00e1 z mnoha bod\u016f, kter\u00e9 mo\u017en\u00e1 Java3D nebude um\u011bt zobrazit.\nOpravdu chcete pokra\u010dovat?
+dialog.3d.warningtracksize=Tato stopa sest\u00e1v\u00e1 z mnoha bod\u016f, kter\u00e9 mo\u017en\u00e1 Java3D nebude um\u011bt zobrazit.\nOpravdu chcete pokra\u010dovat?
+dialog.exportpov.baseimage=Obr\u00e1zek jako podklad
+dialog.exportpov.cannotmakebaseimage=Nelze zapsat podklad
+dialog.baseimage.title=Podklad
+dialog.baseimage.useimage=Pou\u017e\u00edt obr\u00e1zek
+dialog.baseimage.mapsource=Zdroj mapy
+dialog.baseimage.zoom=Zv\u011bt\u0161en\u00ed
+dialog.baseimage.incomplete=Obr\u00e1zek ne\u00fapln\u00fd
+dialog.baseimage.tiles=Dla\u017edic
+dialog.baseimage.size=Velikost obr\u00e1zku
 dialog.exportsvg.text=Zvolte parametry exportu do SVG
 dialog.exportsvg.phi=Azimut \u03d5
 dialog.exportsvg.theta=V\u00fd\u0161kov\u00fd \u00fahel \u03b8
 dialog.exportsvg.gradients=Vypl\u0148ovat body barevn\u00fdm p\u0159echodem
 dialog.exportsvg.text=Zvolte parametry exportu do SVG
 dialog.exportsvg.phi=Azimut \u03d5
 dialog.exportsvg.theta=V\u00fd\u0161kov\u00fd \u00fahel \u03b8
 dialog.exportsvg.gradients=Vypl\u0148ovat body barevn\u00fdm p\u0159echodem
+dialog.exportimage.noimagepossible=Aby bylo mo\u017en\u00e9 mapu ulo\u017eit jako obr\u00e1zek, je t\u0159eba st\u00e1hnout a ulo\u017eit dla\u017edice
+dialog.exportimage.drawtrack=Nakreslit stopu na mapu
+dialog.exportimage.textscalepercent=Zv\u011bt\u0161en\u00ed fontu (%)
 dialog.pointtype.desc=Ulo\u017eit body n\u00e1sleduj\u00edc\u00edch typ\u016f:
 dialog.pointtype.desc=Ulo\u017eit body n\u00e1sleduj\u00edc\u00edch typ\u016f:
-dialog.pointtype.track=Body trasy
+dialog.pointtype.track=Body stopy
 dialog.pointtype.waypoint=V\u00fdzna\u010dn\u00e9 body
 dialog.pointtype.photo=M\u00edsta s fotografiemi
 dialog.pointtype.audio=M\u00edsta s audionahr\u00e1vkami
 dialog.pointtype.selection=Jen v\u00fdb\u011br
 dialog.confirmreversetrack.title=Potvr\u010fte obr\u00e1cen\u00ed
 dialog.pointtype.waypoint=V\u00fdzna\u010dn\u00e9 body
 dialog.pointtype.photo=M\u00edsta s fotografiemi
 dialog.pointtype.audio=M\u00edsta s audionahr\u00e1vkami
 dialog.pointtype.selection=Jen v\u00fdb\u011br
 dialog.confirmreversetrack.title=Potvr\u010fte obr\u00e1cen\u00ed
-dialog.confirmreversetrack.text=Tato trasa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se obr\u00e1cen\u00edm zm\u011bn\u00ed.\nOpravdu chcete obr\u00e1tit v\u00fdb\u011br?
+dialog.confirmreversetrack.text=Tato stopa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se obr\u00e1cen\u00edm zm\u011bn\u00ed.\nOpravdu chcete obr\u00e1tit v\u00fdb\u011br?
 dialog.confirmcutandmove.title=Potvr\u010fte p\u0159esun
 dialog.confirmcutandmove.title=Potvr\u010fte p\u0159esun
-dialog.confirmcutandmove.text=Tato trasa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se p\u0159esunem zm\u011bn\u00ed.\nOpravdu chcete v\u00fdb\u011br p\u0159esunout?
+dialog.confirmcutandmove.text=Tato stopa obsahuje \u010dasov\u00e9 zna\u010dky, jejich\u017e po\u0159ad\u00ed se p\u0159esunem zm\u011bn\u00ed.\nOpravdu chcete v\u00fdb\u011br p\u0159esunout?
 dialog.interpolate.parameter.text=Po\u010det bod\u016f, kter\u00e9 se maj\u00ed vlo\u017eit mezi ka\u017ed\u00e9 dva po sob\u011b jdouc\u00ed body
 dialog.interpolate.parameter.text=Po\u010det bod\u016f, kter\u00e9 se maj\u00ed vlo\u017eit mezi ka\u017ed\u00e9 dva po sob\u011b jdouc\u00ed body
-dialog.interpolate.betweenwaypoints=Vlo\u017eit nov\u00e9 body trasy mezi v\u00fdzna\u010dn\u00fdmi body?
+dialog.interpolate.betweenwaypoints=Vlo\u017eit nov\u00e9 body stopy mezi v\u00fdzna\u010dn\u00fdmi body?
 dialog.undo.title=Vr\u00e1tit akci (akce)
 dialog.undo.pretext=Pros\u00edm vyberte akci (akce) k vr\u00e1cen\u00ed
 dialog.undo.none.title=Nelze vr\u00e1tit
 dialog.undo.title=Vr\u00e1tit akci (akce)
 dialog.undo.pretext=Pros\u00edm vyberte akci (akce) k vr\u00e1cen\u00ed
 dialog.undo.none.title=Nelze vr\u00e1tit
@@ -233,7 +278,9 @@ dialog.clearundo.title=Vypr\u00e1zdnit pam\u011b\u0165 undo
 dialog.clearundo.text=Opravdu chcete vypr\u00e1zdnit pam\u011b\u0165 undo?\nNebude u\u017e mo\u017en\u00e9 akce vracet do p\u016fvodn\u00edho stavu!
 dialog.pointedit.title=Upravit bod
 dialog.pointedit.text=Vyberte pole k editaci a stiskn\u011bte 'Upravit'
 dialog.clearundo.text=Opravdu chcete vypr\u00e1zdnit pam\u011b\u0165 undo?\nNebude u\u017e mo\u017en\u00e9 akce vracet do p\u016fvodn\u00edho stavu!
 dialog.pointedit.title=Upravit bod
 dialog.pointedit.text=Vyberte pole k editaci a stiskn\u011bte 'Upravit'
+dialog.pointedit.intro=Vyberte pole k zobrazen\u00ed a editaci hodnoty
 dialog.pointedit.table.field=Pole
 dialog.pointedit.table.field=Pole
+dialog.pointedit.nofield=Nen\u00ed vybr\u00e1no \u017e\u00e1dn\u00e9 pole
 dialog.pointedit.table.value=Hodnota
 dialog.pointedit.table.changed=Zm\u011bn\u011bno
 dialog.pointedit.changevalue.text=Zadejte novou hodnotu pole
 dialog.pointedit.table.value=Hodnota
 dialog.pointedit.table.changed=Zm\u011bn\u011bno
 dialog.pointedit.changevalue.text=Zadejte novou hodnotu pole
@@ -269,7 +316,7 @@ dialog.charts.screen=V\u00fdstup na obrazovku
 dialog.charts.svg=V\u00fdstup do souboru SVG
 dialog.charts.svgwidth=\u0160\u00ed\u0159ka SVG
 dialog.charts.svgheight=V\u00fd\u0161ka SVG
 dialog.charts.svg=V\u00fdstup do souboru SVG
 dialog.charts.svgwidth=\u0160\u00ed\u0159ka SVG
 dialog.charts.svgheight=V\u00fd\u0161ka SVG
-dialog.charts.needaltitudeortimes=Trasa mus\u00ed obsahovat bu\u010f informaci o nadmo\u0159sk\u00e9 v\u00fd\u0161ce nebo o \u010dase, aby bylo mo\u017en\u00e9 vytv\u00e1\u0159et grafy
+dialog.charts.needaltitudeortimes=Stopa mus\u00ed obsahovat bu\u010f informaci o nadmo\u0159sk\u00e9 v\u00fd\u0161ce nebo o \u010dase, aby bylo mo\u017en\u00e9 vytv\u00e1\u0159et grafy
 dialog.charts.gnuplotnotfound=Nepoda\u0159ilo se nal\u00e9zt gnuplot na dan\u00e9m um\u00edst\u011bn\u00ed
 dialog.distances.intro=P\u0159\u00edm\u00e9 vzd\u00e1lenosti mezi body
 dialog.distances.column.from=Z bodu
 dialog.charts.gnuplotnotfound=Nepoda\u0159ilo se nal\u00e9zt gnuplot na dan\u00e9m um\u00edst\u011bn\u00ed
 dialog.distances.intro=P\u0159\u00edm\u00e9 vzd\u00e1lenosti mezi body
 dialog.distances.column.from=Z bodu
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Je t\u0159eba zadat v\u00edce bod\u016f, aby bylo
 dialog.fullrangedetails.intro=Zobrazuji detaily vybran\u00e9ho rozmez\u00ed
 dialog.fullrangedetails.coltotal=V\u010detn\u011b p\u0159eru\u0161en\u00ed
 dialog.fullrangedetails.colsegments=Bez p\u0159eru\u0161en\u00ed
 dialog.fullrangedetails.intro=Zobrazuji detaily vybran\u00e9ho rozmez\u00ed
 dialog.fullrangedetails.coltotal=V\u010detn\u011b p\u0159eru\u0161en\u00ed
 dialog.fullrangedetails.colsegments=Bez p\u0159eru\u0161en\u00ed
+dialog.estimatetime.details=Podrobnosti
+dialog.estimatetime.gentle=M\u00edrn\u00e9
+dialog.estimatetime.steep=Prudk\u00e9
+dialog.estimatetime.climb=Stoup\u00e1n\u00ed
+dialog.estimatetime.descent=Kles\u00e1n\u00ed
+dialog.estimatetime.parameters=Parametry
+dialog.estimatetime.parameters.timefor=\u010cas k
+dialog.estimatetime.results=V\u00fdsledky
+dialog.estimatetime.results.estimatedtime=Odhad \u010dasu
+dialog.estimatetime.results.actualtime=Aktu\u00e1ln\u00ed \u010das
+dialog.estimatetime.error.nodistance=Aby bylo mo\u017en\u00e9 odhadnout \u010das, je t\u0159eba vybrat body jedn\u00e9 stopy
+dialog.estimatetime.error.noaltitudes=V\u00fdb\u011br neobsahuje \u00fadaje o nadmo\u0159sk\u00e9 v\u00fd\u0161ce
+dialog.learnestimationparams.intro=Parametry vypo\u010d\u00edtan\u00e9 podle t\u00e9to stopy
+dialog.learnestimationparams.averageerror=Pr\u016fm\u011brn\u00e1 chyba
+dialog.learnestimationparams.combine=Tyto parametry je mo\u017en\u00e9 zkombinovat s aktu\u00e1ln\u00edmi hodnotami
+dialog.learnestimationparams.combinedresults=V\u00fdsledn\u00e9 hodnoty
+dialog.learnestimationparams.weight.100pccurrent=Ponechat st\u00e1vaj\u00edc\u00ed hodnoty
+dialog.learnestimationparams.weight.current=st\u00e1vaj\u00edc\u00ed
+dialog.learnestimationparams.weight.calculated=vypo\u010dten\u00e9
+dialog.learnestimationparams.weight.50pc=Pr\u016fm\u011br st\u00e1vaj\u00edc\u00edch a vypo\u010d\u00edtan\u00fdch hodnot
+dialog.learnestimationparams.weight.100pccalculated=Pou\u017e\u00edt vypo\u010d\u00edtan\u00e9 hodnoty
 dialog.setmapbg.intro=Vyberte jeden ze zdroj\u016f map nebo p\u0159idejte nov\u00fd
 dialog.addmapsource.title=P\u0159idat nov\u00fd zdroj map
 dialog.addmapsource.sourcename=N\u00e1zev zdroje
 dialog.setmapbg.intro=Vyberte jeden ze zdroj\u016f map nebo p\u0159idejte nov\u00fd
 dialog.addmapsource.title=P\u0159idat nov\u00fd zdroj map
 dialog.addmapsource.sourcename=N\u00e1zev zdroje
@@ -287,15 +355,15 @@ dialog.addmapsource.layer2url=Voliteln\u011b URL druh\u00e9 vrstvy
 dialog.addmapsource.maxzoom=Maxim\u00e1ln\u00ed zv\u011bt\u0161en\u00ed
 dialog.addmapsource.cloudstyle=\u010c\u00edslo stylu
 dialog.addmapsource.noname=Bez n\u00e1zvu
 dialog.addmapsource.maxzoom=Maxim\u00e1ln\u00ed zv\u011bt\u0161en\u00ed
 dialog.addmapsource.cloudstyle=\u010c\u00edslo stylu
 dialog.addmapsource.noname=Bez n\u00e1zvu
-dialog.gpsies.column.name=N\u00e1zev trasy
+dialog.gpsies.column.name=N\u00e1zev stopy
 dialog.gpsies.column.length=D\u00e9lka
 dialog.gpsies.description=Popis
 dialog.gpsies.nodescription=Bez popisu
 dialog.gpsies.column.length=D\u00e9lka
 dialog.gpsies.description=Popis
 dialog.gpsies.nodescription=Bez popisu
-dialog.gpsies.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 trasy
+dialog.gpsies.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 stopy
 dialog.gpsies.username=U\u017eiv. jm\u00e9no k gpsies
 dialog.gpsies.password=Heslo k gpsies
 dialog.gpsies.username=U\u017eiv. jm\u00e9no k gpsies
 dialog.gpsies.password=Heslo k gpsies
-dialog.gpsies.keepprivate=Trasu nezve\u0159ej\u0148ovat
-dialog.gpsies.confirmopenpage=Otev\u0159\u00edt nahranou trasu v internetov\u00e9m prohl\u00ed\u017ee\u010di?
+dialog.gpsies.keepprivate=Stopu nezve\u0159ej\u0148ovat
+dialog.gpsies.confirmopenpage=Otev\u0159\u00edt nahranou stopu v internetov\u00e9m prohl\u00ed\u017ee\u010di?
 dialog.gpsies.activities=Aktivita
 dialog.gpsies.activity.trekking=Turistika
 dialog.gpsies.activity.walking=Ch\u016fze
 dialog.gpsies.activities=Aktivita
 dialog.gpsies.activity.trekking=Turistika
 dialog.gpsies.activity.walking=Ch\u016fze
@@ -331,7 +399,7 @@ dialog.correlate.options.timelimit=\u010casov\u00fd limit
 dialog.correlate.options.nodistancelimit=Bez d\u00e9lkov\u00e9ho limitu
 dialog.correlate.options.distancelimit=D\u00e9lkov\u00fd limit
 dialog.correlate.options.correlate=Sladit
 dialog.correlate.options.nodistancelimit=Bez d\u00e9lkov\u00e9ho limitu
 dialog.correlate.options.distancelimit=D\u00e9lkov\u00fd limit
 dialog.correlate.options.correlate=Sladit
-dialog.correlate.alloutsiderange=V\u0161echny polo\u017eky le\u017e\u00ed mimo \u010dasov\u00e9 rozmez\u00ed trasy, tak\u017ee nemohou b\u00fdt slad\u011bny.\nPokuste se zm\u011bnit \u010dasov\u00fd posun nebo ru\u010dn\u011b sla\u010fte aspo\u0148 jednu polo\u017eku.
+dialog.correlate.alloutsiderange=V\u0161echny polo\u017eky le\u017e\u00ed mimo \u010dasov\u00e9 rozmez\u00ed stopy, tak\u017ee nemohou b\u00fdt slad\u011bny.\nPokuste se zm\u011bnit \u010dasov\u00fd posun nebo ru\u010dn\u011b sla\u010fte aspo\u0148 jednu polo\u017eku.
 dialog.correlate.filetimes=\u010cas z\u00e1znamu souboru znamen\u00e1:
 dialog.correlate.filetimes2=audionahr\u00e1vky
 dialog.correlate.correltimes=Sladit tento okam\u017eik nahr\u00e1vky:
 dialog.correlate.filetimes=\u010cas z\u00e1znamu souboru znamen\u00e1:
 dialog.correlate.filetimes2=audionahr\u00e1vky
 dialog.correlate.correltimes=Sladit tento okam\u017eik nahr\u00e1vky:
@@ -358,9 +426,9 @@ dialog.compress.douglaspeucker.title=Douglasova-Peuckerova komprese
 dialog.compress.douglaspeucker.paramdesc=Povolen\u00e1 odchylka
 dialog.compress.summarylabel=Bod\u016f ke smaz\u00e1n\u00ed
 dialog.compress.confirm1=Bylo ozna\u010deno celkem
 dialog.compress.douglaspeucker.paramdesc=Povolen\u00e1 odchylka
 dialog.compress.summarylabel=Bod\u016f ke smaz\u00e1n\u00ed
 dialog.compress.confirm1=Bylo ozna\u010deno celkem
-dialog.compress.confirm2=bod\u016f.\nBody je mo\u017en\u00e9 smazat volbou Trasa->Smazat ozna\u010den\u00e9 body
+dialog.compress.confirm2=bod\u016f.\nBody je mo\u017en\u00e9 smazat volbou Stopa->Smazat ozna\u010den\u00e9 body
 dialog.compress.confirmnone=Nebyly vybr\u00e1ny \u017e\u00e1dn\u00e9 body.
 dialog.compress.confirmnone=Nebyly vybr\u00e1ny \u017e\u00e1dn\u00e9 body.
-dialog.deletemarked.nonefound=Nemohou b\u00fdt odstran\u011bny \u017e\u00e1dn\u00e9 body trasy
+dialog.deletemarked.nonefound=Nemohou b\u00fdt odstran\u011bny \u017e\u00e1dn\u00e9 body stopy
 dialog.pastecoordinates.desc=Zadejte sou\u0159adnice
 dialog.pastecoordinates.coords=Sou\u0159adnice
 dialog.pastecoordinates.nothingfound=Pros\u00edm ov\u011b\u0159te sou\u0159adnice a zkuste znovu
 dialog.pastecoordinates.desc=Zadejte sou\u0159adnice
 dialog.pastecoordinates.coords=Sou\u0159adnice
 dialog.pastecoordinates.nothingfound=Pros\u00edm ov\u011b\u0159te sou\u0159adnice a zkuste znovu
@@ -405,11 +473,11 @@ dialog.checkversion.releasedate1=Tato verze byla vyd\u00e1na
 dialog.checkversion.releasedate2=.
 dialog.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese http://activityworkshop.net/software/gpsprune/download.html.
 dialog.keys.intro=M\u00edsto my\u0161i m\u016f\u017eete pou\u017e\u00edvat n\u00e1sleduj\u00edc\u00ed kl\u00e1vesov\u00e9 zkratky
 dialog.checkversion.releasedate2=.
 dialog.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese http://activityworkshop.net/software/gpsprune/download.html.
 dialog.keys.intro=M\u00edsto my\u0161i m\u016f\u017eete pou\u017e\u00edvat n\u00e1sleduj\u00edc\u00ed kl\u00e1vesov\u00e9 zkratky
-dialog.keys.keylist=<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 trasy</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.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
 dialog.keys.macmodifier=Command
 dialog.saveconfig.desc=N\u00e1sleduj\u00edc\u00ed volby mohou b\u00fdt ulo\u017eeny do konfigura\u010dn\u00edho souboru :
 dialog.keys.normalmodifier=Ctrl
 dialog.keys.macmodifier=Command
 dialog.saveconfig.desc=N\u00e1sleduj\u00edc\u00ed volby mohou b\u00fdt ulo\u017eeny do konfigura\u010dn\u00edho souboru :
-dialog.saveconfig.prune.trackdirectory=Adres\u00e1\u0159 s trasami
+dialog.saveconfig.prune.trackdirectory=Adres\u00e1\u0159 s stopami
 dialog.saveconfig.prune.photodirectory=Ad\u0159es\u00e1\u0159 s fotografiemi
 dialog.saveconfig.prune.languagecode=K\u00f3d jazyka
 dialog.saveconfig.prune.languagefile=Soubor jazyka
 dialog.saveconfig.prune.photodirectory=Ad\u0159es\u00e1\u0159 s fotografiemi
 dialog.saveconfig.prune.languagecode=K\u00f3d jazyka
 dialog.saveconfig.prune.languagefile=Soubor jazyka
@@ -426,7 +494,7 @@ dialog.saveconfig.prune.kmzimagewidth=\u0160\u00ed\u0159ka bitmapy KMZ
 dialog.saveconfig.prune.kmzimageheight=V\u00fd\u0161ka bitmapy KMZ
 dialog.saveconfig.prune.colourscheme=Barevn\u00e9 sch\u00e9ma
 dialog.saveconfig.prune.linewidth=Tlou\u0161\u0165ka \u010d\u00e1ry
 dialog.saveconfig.prune.kmzimageheight=V\u00fd\u0161ka bitmapy KMZ
 dialog.saveconfig.prune.colourscheme=Barevn\u00e9 sch\u00e9ma
 dialog.saveconfig.prune.linewidth=Tlou\u0161\u0165ka \u010d\u00e1ry
-dialog.saveconfig.prune.kmltrackcolour=Barva trasy v KML
+dialog.saveconfig.prune.kmltrackcolour=Barva stopy v KML
 dialog.saveconfig.prune.autosavesettings=Mo\u017enosti ukl\u00e1d\u00e1n\u00ed
 dialog.setpaths.intro=Je-li to t\u0159eba, m\u016f\u017eete nastavit cesty k extern\u00edm aplikac\u00edm:
 dialog.setpaths.found=Cesta nalezena?
 dialog.saveconfig.prune.autosavesettings=Mo\u017enosti ukl\u00e1d\u00e1n\u00ed
 dialog.setpaths.intro=Je-li to t\u0159eba, m\u016f\u017eete nastavit cesty k extern\u00edm aplikac\u00edm:
 dialog.setpaths.found=Cesta nalezena?
@@ -471,16 +539,13 @@ dialog.diskcache.deleted1=Smaz\u00e1no
 dialog.diskcache.deleted2=soubor\u016f z cache
 dialog.deletefieldvalues.intro=Vyberte pole, kter\u00e9 se m\u00e1 z aktu\u00e1ln\u00edho rozmez\u00ed odstranit
 dialog.deletefieldvalues.nofields=V tomto rozmez\u00ed nelze smazat \u017e\u00e1dn\u00e9 pole
 dialog.diskcache.deleted2=soubor\u016f z cache
 dialog.deletefieldvalues.intro=Vyberte pole, kter\u00e9 se m\u00e1 z aktu\u00e1ln\u00edho rozmez\u00ed odstranit
 dialog.deletefieldvalues.nofields=V tomto rozmez\u00ed nelze smazat \u017e\u00e1dn\u00e9 pole
-dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed trasa (1-4)
+dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed stopa (1-4)
 dialog.downloadosm.desc=Potvr\u010fte, \u017ee se maj\u00ed k dan\u00e9 oblasti st\u00e1hnout data OSM:
 dialog.searchwikipedianames.search=Vyhledat:
 
 # 3d window
 dialog.3d.title=Trojrozm\u011brn\u00e9 zobrazen\u00ed GpsPrune
 dialog.3d.altitudefactor=Faktor zd\u016frazn\u011bn\u00ed v\u00fd\u0161ky
 dialog.downloadosm.desc=Potvr\u010fte, \u017ee se maj\u00ed k dan\u00e9 oblasti st\u00e1hnout data OSM:
 dialog.searchwikipedianames.search=Vyhledat:
 
 # 3d window
 dialog.3d.title=Trojrozm\u011brn\u00e9 zobrazen\u00ed GpsPrune
 dialog.3d.altitudefactor=Faktor zd\u016frazn\u011bn\u00ed v\u00fd\u0161ky
-dialog.3dlines.title=Linky m\u0159\u00ed\u017eky GpsPrune
-dialog.3dlines.empty=Nejsou \u017e\u00e1dn\u00e9 linky k zobrazen\u00ed!
-dialog.3dlines.intro=Toto jsou linky m\u0159\u00ed\u017eky trojrozm\u011brn\u00e9ho zobrazen\u00ed
 
 # Confirm messages
 confirm.loadfile=Data na\u010dtena ze souboru
 
 # Confirm messages
 confirm.loadfile=Data na\u010dtena ze souboru
@@ -489,7 +554,7 @@ confirm.save.ok2=bod\u016f do souboru
 confirm.deletepoint.single=bod byl odstran\u011bn
 confirm.deletepoint.multi=body byly odstran\u011bny
 confirm.point.edit=bod zm\u011bn\u011bn
 confirm.deletepoint.single=bod byl odstran\u011bn
 confirm.deletepoint.multi=body byly odstran\u011bny
 confirm.point.edit=bod zm\u011bn\u011bn
-confirm.mergetracksegments=\u010c\u00e1sti trasy spojeny
+confirm.mergetracksegments=\u010c\u00e1sti stopy spojeny
 confirm.reverserange=Rozmez\u00ed obr\u00e1ceno
 confirm.addtimeoffset=\u010casov\u00fd posun zm\u011bn\u011bn
 confirm.addaltitudeoffset=V\u00fd\u0161kov\u00fd posun zm\u011bn\u011bn
 confirm.reverserange=Rozmez\u00ed obr\u00e1ceno
 confirm.addtimeoffset=\u010casov\u00fd posun zm\u011bn\u011bn
 confirm.addaltitudeoffset=V\u00fd\u0161kov\u00fd posun zm\u011bn\u011bn
@@ -529,7 +594,6 @@ button.cancel=Zru\u0161it
 button.overwrite=P\u0159epsat
 button.moveup=Posunout nahoru
 button.movedown=Posunout dol\u016f
 button.overwrite=P\u0159epsat
 button.moveup=Posunout nahoru
 button.movedown=Posunout dol\u016f
-button.showlines=Zobrazit linky m\u0159\u00ed\u017eky
 button.edit=Editovat
 button.exit=Konec
 button.close=Zav\u0159\u00edt
 button.edit=Editovat
 button.exit=Konec
 button.close=Zav\u0159\u00edt
@@ -552,6 +616,7 @@ button.browse=Proch\u00e1zet...
 button.addnew=P\u0159idat nov\u00e9
 button.delete=Smazat
 button.manage=Upravit
 button.addnew=P\u0159idat nov\u00e9
 button.delete=Smazat
 button.manage=Upravit
+button.combine=Zkombinovat
 
 # File types
 filetype.txt=soubory TXT
 
 # File types
 filetype.txt=soubory TXT
@@ -562,14 +627,16 @@ filetype.kmz=soubory KMZ
 filetype.gpx=soubory GPX
 filetype.pov=soubory POV
 filetype.svg=soubory SVG
 filetype.gpx=soubory GPX
 filetype.pov=soubory POV
 filetype.svg=soubory SVG
+filetype.png=soubory PNG
 filetype.audio=soubory MP3, OGG, WAV
 
 # Display components
 display.nodata=\u017d\u00e1dn\u00e1 data
 filetype.audio=soubory MP3, OGG, WAV
 
 # Display components
 display.nodata=\u017d\u00e1dn\u00e1 data
-display.noaltitudes=Trasa neobsahuje informace o v\u00fd\u0161ce
-display.notimestamps=Trasa neobsahuje \u010dasov\u00e9 zna\u010dky
-details.trackdetails=Detaily trasy
-details.notrack=\u017d\u00e1dn\u00e1 trasa
+display.noaltitudes=Stopa neobsahuje informace o v\u00fd\u0161ce
+display.notimestamps=Stopa neobsahuje \u010dasov\u00e9 zna\u010dky
+display.novalues=Stopa neobsahuje hodnoty tohoto pole
+details.trackdetails=Detaily stopy
+details.notrack=\u017d\u00e1dn\u00e1 stopa
 details.track.points=Body
 details.track.file=Soubor
 details.track.numfiles=Po\u010det soubor\u016f
 details.track.points=Body
 details.track.file=Soubor
 details.track.numfiles=Po\u010det soubor\u016f
@@ -638,21 +705,31 @@ units.feet=Stopy
 units.feet.short=stop
 units.kilometres=Kilometry
 units.kilometres.short=km
 units.feet.short=stop
 units.kilometres=Kilometry
 units.kilometres.short=km
+units.kilometresperhour=km za hodinu
 units.kilometresperhour.short=km/h
 units.miles=M\u00edle
 units.miles.short=mil
 units.kilometresperhour.short=km/h
 units.miles=M\u00edle
 units.miles.short=mil
+units.milesperhour=mil za hodinu
 units.milesperhour.short=mph
 units.nauticalmiles=N\u00e1mo\u0159n\u00ed m\u00edle
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=uzly
 units.milesperhour.short=mph
 units.nauticalmiles=N\u00e1mo\u0159n\u00ed m\u00edle
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=uzly
+units.metrespersec=metr\u016f za sekundu
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=stop za sekundu
 units.feetpersec.short=stop/s
 units.hours=hodin
 units.feetpersec.short=stop/s
 units.hours=hodin
+units.minutes=minut
+units.seconds=sekund
 units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Stupn\u011b
 units.iso8601=ISO 8601
 
 units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Stupn\u011b
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=a
+logic.or=nebo
+
 # External urls
 url.googlemaps=maps.google.cz
 wikipedia.lang=cs
 # External urls
 url.googlemaps=maps.google.cz
 wikipedia.lang=cs
@@ -672,11 +749,11 @@ undo.deletepoint=smazat bod
 undo.removephoto=odebrat fotografii
 undo.removeaudio=odebrat audionahr\u00e1vku
 undo.deleterange=smazat rozmez\u00ed
 undo.removephoto=odebrat fotografii
 undo.removeaudio=odebrat audionahr\u00e1vku
 undo.deleterange=smazat rozmez\u00ed
-undo.croptrack=o\u0159\u00edznout trasu
-undo.deletemarked=zkomprimovat trasu
+undo.croptrack=o\u0159\u00edznout stopu
+undo.deletemarked=zkomprimovat stopu
 undo.insert=vlo\u017eit body
 undo.reverse=obr\u00e1tit rozmez\u00ed
 undo.insert=vlo\u017eit body
 undo.reverse=obr\u00e1tit rozmez\u00ed
-undo.mergetracksegments=slou\u010dit \u010d\u00e1sti trasy
+undo.mergetracksegments=slou\u010dit \u010d\u00e1sti stopy
 undo.addtimeoffset=p\u0159idat \u010dasov\u00fd posun
 undo.addaltitudeoffset=p\u0159idat v\u00fd\u0161kov\u00fd posun
 undo.rearrangewaypoints=p\u0159euspo\u0159\u00e1dat body
 undo.addtimeoffset=p\u0159idat \u010dasov\u00fd posun
 undo.addaltitudeoffset=p\u0159idat v\u00fd\u0161kov\u00fd posun
 undo.rearrangewaypoints=p\u0159euspo\u0159\u00e1dat body
@@ -721,7 +798,7 @@ error.undofailed.text=Nepoda\u0159ilo se vr\u00e1tit operaci
 error.function.noop.title=Operace nic neprovedla
 error.rearrange.noop=P\u0159euspo\u0159\u00e1d\u00e1n\u00ed nic neprovedlo
 error.function.notavailable.title=Funkce nen\u00ed dostupn\u00e1
 error.function.noop.title=Operace nic neprovedla
 error.rearrange.noop=P\u0159euspo\u0159\u00e1d\u00e1n\u00ed nic neprovedlo
 error.function.notavailable.title=Funkce nen\u00ed dostupn\u00e1
-error.function.nojava3d=Tato funkce vy\u017eaduje knihovnu Java3d,\ndostupnou na Sun.com.
+error.function.nojava3d=Tato funkce vy\u017eaduje knihovnu Java3d.
 error.3d=P\u0159i trojrozm\u011brn\u00e9m zobrazen\u00ed do\u0161lo k chyb\u011b
 error.readme.notfound=Nenalezen soubor readme
 error.osmimage.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed mapov\u00fdch podklad\u016f
 error.3d=P\u0159i trojrozm\u011brn\u00e9m zobrazen\u00ed do\u0161lo k chyb\u011b
 error.readme.notfound=Nenalezen soubor readme
 error.osmimage.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed mapov\u00fdch podklad\u016f
@@ -738,3 +815,4 @@ error.cache.notthere=Nepoda\u0159ilo se nal\u00e9zt adres\u00e1\u0159 s cache ma
 error.cache.empty=Adres\u00e1\u0159 s cache map je pr\u00e1zdn\u00fd.
 error.cache.cannotdelete=Nelze smazat soubory map.
 error.interpolate.invalidparameter=Po\u010det bod\u016f mus\u00ed b\u00fdt mezi 1 a 1000
 error.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.
index b375fe34daca8e58ba5ce55b6788c66c2b863413..ea394b97388e35d75b68355379b12ab6d4e8782c 100644 (file)
@@ -80,6 +80,5 @@ function.show3d=3-D view
 function.distances=Afstande
 function.fullrangedetails=Vis alle detaljer
 function.setmapbg=V\u00e6lg kort som baggrund
 function.distances=Afstande
 function.fullrangedetails=Vis alle detaljer
 function.setmapbg=V\u00e6lg kort som baggrund
-function.setkmzimagesize=V\u00e6lg KMZ billedst\u00f8rrelse
 function.setpaths=V\u00e6lg sti til programmer
 function.getgpsies=Se liste af GPS-spor
 function.setpaths=V\u00e6lg sti til programmer
 function.getgpsies=Se liste af GPS-spor
index ee57c2152143d5d110b10b8d5e97bd7f3cddb192..4914e44ae0546c3c235f0e2240b86bead6c0a090 100644 (file)
@@ -84,6 +84,7 @@ function.exportkml=KML exportieren
 function.exportgpx=GPX exportieren
 function.exportpov=POV exportieren
 function.exportsvg=SVG exportieren
 function.exportgpx=GPX exportieren
 function.exportpov=POV exportieren
 function.exportsvg=SVG exportieren
+function.exportimage=Bild exportieren
 function.editwaypointname=Name des Punkts bearbeiten
 function.compress=Track komprimieren
 function.deleterange=Bereich l\u00f6schen
 function.editwaypointname=Name des Punkts bearbeiten
 function.compress=Track komprimieren
 function.deleterange=Bereich l\u00f6schen
@@ -99,8 +100,9 @@ function.charts=Diagramme
 function.show3d=3D Ansicht
 function.distances=Entfernungen
 function.fullrangedetails=Zus\u00e4tzliche Bereichdetails
 function.show3d=3D Ansicht
 function.distances=Entfernungen
 function.fullrangedetails=Zus\u00e4tzliche Bereichdetails
+function.estimatetime=Zeit absch\u00e4tzen
+function.learnestimationparams=Zeitparameter erlernen
 function.setmapbg=Karte Hintergrund setzen
 function.setmapbg=Karte Hintergrund setzen
-function.setkmzimagesize=Bildgr\u00f6\u00dfe im KMZ setzen
 function.setpaths=Programmpfade setzen
 function.getgpsies=Tracks bei GPSies.com herunterladen
 function.uploadgpsies=Track zu GPSies.com hochladen
 function.setpaths=Programmpfade setzen
 function.getgpsies=Tracks bei GPSies.com herunterladen
 function.uploadgpsies=Track zu GPSies.com hochladen
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=Datens\u00e4tze, mit
 dialog.openoptions.deliminfo.fields=Feldern
 dialog.openoptions.deliminfo.norecords=Keine Datens\u00e4tze
 dialog.openoptions.altitudeunits=Ma\u00dfeinheiten f\u00fcr die H\u00f6he
 dialog.openoptions.deliminfo.fields=Feldern
 dialog.openoptions.deliminfo.norecords=Keine Datens\u00e4tze
 dialog.openoptions.altitudeunits=Ma\u00dfeinheiten f\u00fcr die H\u00f6he
+dialog.openoptions.speedunits=Ma\u00dfeinheiten f\u00fcr die Geschwindigkeiten
+dialog.openoptions.vertspeedunits=Ma\u00dfeinheiten f\u00fcr vertikale Geschwindigkeiten
+dialog.openoptions.vspeed.positiveup=Positive Geschwindigkeiten aufw\u00e4rts
+dialog.openoptions.vspeed.positivedown=Positive Geschwindigkeiten abw\u00e4rts
 dialog.open.contentsdoubled=Diese Datei enth\u00e4lt zwei Kopien jedes Punkts,\neinmal als Waypoint und einmal als Trackpunkt.
 dialog.selecttracks.intro=W\u00e4hlen Sie den Track oder die Tracks aus, die Sie laden m\u00f6chten
 dialog.selecttracks.noname=Unbenannt
 dialog.open.contentsdoubled=Diese Datei enth\u00e4lt zwei Kopien jedes Punkts,\neinmal als Waypoint und einmal als Trackpunkt.
 dialog.selecttracks.intro=W\u00e4hlen Sie den Track oder die Tracks aus, die Sie laden m\u00f6chten
 dialog.selecttracks.noname=Unbenannt
@@ -175,7 +181,31 @@ dialog.gpsload.gettracks=Tracks laden
 dialog.gpsload.save=Als Datei speichern
 dialog.gpssend.sendwaypoints=Wegpunkte senden
 dialog.gpssend.sendtracks=Tracks senden
 dialog.gpsload.save=Als Datei speichern
 dialog.gpssend.sendwaypoints=Wegpunkte senden
 dialog.gpssend.sendtracks=Tracks senden
-dialog.gpssend.trackname=Track Name
+dialog.gpssend.trackname=Trackname
+dialog.gpsbabel.filters=Filter
+dialog.addfilter.title=Filter einf\u00fcgen
+dialog.gpsbabel.filter.discard=Wegwerfen
+dialog.gpsbabel.filter.simplify=Vereinfachen
+dialog.gpsbabel.filter.distance=Distanz
+dialog.gpsbabel.filter.interpolate=Interpolieren
+dialog.gpsbabel.filter.discard.intro=Punkte wegwerfen, falls
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Anzahl Satelliten <
+dialog.gpsbabel.filter.discard.nofix=Punkt kein Fix hat
+dialog.gpsbabel.filter.discard.unknownfix=Punkt unbekanntes Fix hat
+dialog.gpsbabel.filter.simplify.intro=Punkte entfernen bis
+dialog.gpsbabel.filter.simplify.maxpoints=Anzahl Punkte <
+dialog.gpsbabel.filter.simplify.maxerror=oder Fehlerdistanz <
+dialog.gpsbabel.filter.simplify.crosstrack=Distanz quer
+dialog.gpsbabel.filter.simplify.length=L\u00e4ngendifferenz
+dialog.gpsbabel.filter.simplify.relative=Relativ zum Hdop
+dialog.gpsbabel.filter.distance.intro=Punkte entfernen, die in der N\u00e4he von fr\u00fcheren Punkten sind
+dialog.gpsbabel.filter.distance.distance=Falls Distanz <
+dialog.gpsbabel.filter.distance.time=und Zeitdifferenz <
+dialog.gpsbabel.filter.interpolate.intro=Zus\u00e4tzliche Punkte hineinf\u00fcgen
+dialog.gpsbabel.filter.interpolate.distance=Falls Distanz >
+dialog.gpsbabel.filter.interpolate.time=oder Zeitdifferenz >
 dialog.saveoptions.title=Datei speichern
 dialog.save.fieldstosave=Zu speichernde Felder
 dialog.save.table.field=Feld
 dialog.saveoptions.title=Datei speichern
 dialog.save.fieldstosave=Zu speichernde Felder
 dialog.save.table.field=Feld
@@ -192,7 +222,10 @@ dialog.exportkml.text=Titel f\u00fcr die Daten
 dialog.exportkml.altitude=Absolute H\u00f6heninformation (f\u00fcr Luftfahrt)
 dialog.exportkml.kmz=Daten in KMZ-Datei komprimieren
 dialog.exportkml.exportimages=Vorschaubilder mit in KMZ-Datei exportieren
 dialog.exportkml.altitude=Absolute H\u00f6heninformation (f\u00fcr Luftfahrt)
 dialog.exportkml.kmz=Daten in KMZ-Datei komprimieren
 dialog.exportkml.exportimages=Vorschaubilder mit in KMZ-Datei exportieren
+dialog.exportkml.imagesize=Bildgr\u00f6\u00dfe
 dialog.exportkml.trackcolour=Trackfarbe
 dialog.exportkml.trackcolour=Trackfarbe
+dialog.exportkml.standardkml=Standardes KML
+dialog.exportkml.extendedkml=Erweitertes KML mit Zeitstempeln
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschreibung
 dialog.exportgpx.includetimestamps=Zeitstempel mit exportieren
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschreibung
 dialog.exportgpx.includetimestamps=Zeitstempel mit exportieren
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Kamera Z
 dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Stangen
 dialog.exportpov.tubesandwalls=R\u00f6hren und W\u00e4nde
 dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Stangen
 dialog.exportpov.tubesandwalls=R\u00f6hren und W\u00e4nde
-dialog.exportpov.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nM\u00f6chten Sie den Vorgang trotzdem fortsetzen?
+dialog.3d.warningtracksize=Dieser Track hat sehr viele Punkte, die Java3D vielleicht nicht bearbeiten kann.\nM\u00f6chten Sie den Vorgang trotzdem fortsetzen?
+dialog.exportpov.baseimage=Grundbild
+dialog.exportpov.cannotmakebaseimage=Bild kann nicht gespeichert werden
+dialog.baseimage.title=Kartenbild
+dialog.baseimage.useimage=Bild verwenden
+dialog.baseimage.mapsource=Kartenquelle
+dialog.baseimage.zoom=Zoomstufe
+dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
+dialog.baseimage.tiles=Kacheln
+dialog.baseimage.size=Bildgr\u00f6\u00dfe
 dialog.exportsvg.text=W\u00e4hlen Sie die Parameter f\u00fcr den SVG-Export aus
 dialog.exportsvg.phi=Richtungswinkel \u03d5
 dialog.exportsvg.theta=Neigungswinkel \u03b8
 dialog.exportsvg.gradients=Farbverl\u00e4ufe verwenden
 dialog.exportsvg.text=W\u00e4hlen Sie die Parameter f\u00fcr den SVG-Export aus
 dialog.exportsvg.phi=Richtungswinkel \u03d5
 dialog.exportsvg.theta=Neigungswinkel \u03b8
 dialog.exportsvg.gradients=Farbverl\u00e4ufe verwenden
+dialog.exportimage.noimagepossible=Kartenbilder m\u00fcssen schon gespeichert werden bevor sie in einem Export verwendet werden k\u00f6nnen
+dialog.exportimage.drawtrack=Track auf der Karte zeichnen
+dialog.exportimage.textscalepercent=Text Skalierung (%)
 dialog.pointtype.desc=Folgende Punkttypen speichern:
 dialog.pointtype.track=Trackpunkte
 dialog.pointtype.waypoint=Wegpunkte
 dialog.pointtype.desc=Folgende Punkttypen speichern:
 dialog.pointtype.track=Trackpunkte
 dialog.pointtype.waypoint=Wegpunkte
@@ -233,7 +278,9 @@ dialog.clearundo.title=Undo-Liste l\u00f6schen
 dialog.clearundo.text=Wollen Sie wirklich die Undo-Liste l\u00f6schen?\nAlle Undo- Informationen werden verloren gehen!
 dialog.pointedit.title=Punkt bearbeiten
 dialog.pointedit.text=W\u00e4hlen Sie die Felder aus, die Sie bearbeiten m\u00f6chten, und verwenden Sie den 'Bearbeiten'-Button, um den Wert zu \u00e4ndern
 dialog.clearundo.text=Wollen Sie wirklich die Undo-Liste l\u00f6schen?\nAlle Undo- Informationen werden verloren gehen!
 dialog.pointedit.title=Punkt bearbeiten
 dialog.pointedit.text=W\u00e4hlen Sie die Felder aus, die Sie bearbeiten m\u00f6chten, und verwenden Sie den 'Bearbeiten'-Button, um den Wert zu \u00e4ndern
+dialog.pointedit.intro=W\u00e4hlen Sie die Felder aus um die Werte zu sehen und bearbeiten
 dialog.pointedit.table.field=Feld
 dialog.pointedit.table.field=Feld
+dialog.pointedit.nofield=Keinen Feld ausgew\u00e4hlt
 dialog.pointedit.table.value=Wert
 dialog.pointedit.table.changed=Ge\u00e4ndert
 dialog.pointedit.changevalue.text=Geben Sie den neuen Wert f\u00fcr dieses Feld ein
 dialog.pointedit.table.value=Wert
 dialog.pointedit.table.changed=Ge\u00e4ndert
 dialog.pointedit.changevalue.text=Geben Sie den neuen Wert f\u00fcr dieses Feld ein
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Diese Funktion braucht Wegpunkte, um die Distanzen
 dialog.fullrangedetails.intro=Detaillierte Angaben zum markierten Bereich
 dialog.fullrangedetails.coltotal=Mit L\u00fccken
 dialog.fullrangedetails.colsegments=Ohne L\u00fccken
 dialog.fullrangedetails.intro=Detaillierte Angaben zum markierten Bereich
 dialog.fullrangedetails.coltotal=Mit L\u00fccken
 dialog.fullrangedetails.colsegments=Ohne L\u00fccken
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Leicht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Aufstieg
+dialog.estimatetime.descent=Abstieg
+dialog.estimatetime.parameters=Parameter
+dialog.estimatetime.parameters.timefor=Zeit f\u00fcr
+dialog.estimatetime.results=Ergebnisse
+dialog.estimatetime.results.estimatedtime=Abgesch\u00e4tzte Zeit
+dialog.estimatetime.results.actualtime=Gebrauchte Zeit
+dialog.estimatetime.error.nodistance=Die Absch\u00e4tzungen brauchen zusammengebundete Punkte mit einer Distanz
+dialog.estimatetime.error.noaltitudes=Der Bereich enth\u00e4lt keine H\u00f6heninformation
+dialog.learnestimationparams.intro=Hier sind die Parameter die aus diesem Track berechnet wurden
+dialog.learnestimationparams.averageerror=Fehler
+dialog.learnestimationparams.combine=Diese Parameter k\u00f6nnen mit den aktuellen Werten zusammengeschlossen werden
+dialog.learnestimationparams.combinedresults=Zusammengeschlossenen Ergebnisse
+dialog.learnestimationparams.weight.100pccurrent=Aktuelle Werte behalten
+dialog.learnestimationparams.weight.current=aktuell
+dialog.learnestimationparams.weight.calculated=berechnet
+dialog.learnestimationparams.weight.50pc=Mittelwert zwischen aktuellen und berechneten Werten
+dialog.learnestimationparams.weight.100pccalculated=Neue berechnete Werte \u00fcbernehmen
 dialog.setmapbg.intro=Eine der Quellen ausw\u00e4hlen oder eine neue hinzuf\u00fcgen
 dialog.addmapsource.title=Neue Kartenquelle hinzuf\u00fcgen
 dialog.addmapsource.sourcename=Name der Quelle
 dialog.setmapbg.intro=Eine der Quellen ausw\u00e4hlen oder eine neue hinzuf\u00fcgen
 dialog.addmapsource.title=Neue Kartenquelle hinzuf\u00fcgen
 dialog.addmapsource.sourcename=Name der Quelle
@@ -422,8 +490,7 @@ dialog.saveconfig.prune.exiftoolpath=ExifTool-Pfad
 dialog.saveconfig.prune.mapsource=Kartenserver-Index
 dialog.saveconfig.prune.mapsourcelist=Kartenserver
 dialog.saveconfig.prune.diskcache=Kartenordner
 dialog.saveconfig.prune.mapsource=Kartenserver-Index
 dialog.saveconfig.prune.mapsourcelist=Kartenserver
 dialog.saveconfig.prune.diskcache=Kartenordner
-dialog.saveconfig.prune.kmzimagewidth=Bildbreite in KMZ
-dialog.saveconfig.prune.kmzimageheight=Bildh\u00f6he in KMZ
+dialog.saveconfig.prune.kmzimagewidth=Bildgr\u00f6\u00dfe in KMZ
 dialog.saveconfig.prune.colourscheme=Farbschema
 dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML-Trackfarbe
 dialog.saveconfig.prune.colourscheme=Farbschema
 dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML-Trackfarbe
@@ -478,9 +545,6 @@ dialog.searchwikipedianames.search=Suche nach:
 # 3d window
 dialog.3d.title=GpsPrune-3D-Ansicht
 dialog.3d.altitudefactor=Vervielfachungsfaktor f\u00fcr H\u00f6hen
 # 3d window
 dialog.3d.title=GpsPrune-3D-Ansicht
 dialog.3d.altitudefactor=Vervielfachungsfaktor f\u00fcr H\u00f6hen
-dialog.3dlines.title=GpsPrune-Gitterlinien
-dialog.3dlines.empty=Keine Linien zum Anzeigen!
-dialog.3dlines.intro=Hier sind die Linien f\u00fcr die 3D Ansicht
 
 # Confirm messages
 confirm.loadfile=Daten aus Datei geladen
 
 # Confirm messages
 confirm.loadfile=Daten aus Datei geladen
@@ -529,7 +593,6 @@ button.cancel=Abbrechen
 button.overwrite=\u00dcberschreiben
 button.moveup=Nach oben
 button.movedown=Nach unten
 button.overwrite=\u00dcberschreiben
 button.moveup=Nach oben
 button.movedown=Nach unten
-button.showlines=Linien anzeigen
 button.edit=Bearbeiten
 button.exit=Beenden
 button.close=Schlie\u00dfen
 button.edit=Bearbeiten
 button.exit=Beenden
 button.close=Schlie\u00dfen
@@ -552,6 +615,7 @@ button.browse=Durchsuchen...
 button.addnew=Hinzuf\u00fcgen
 button.delete=Entfernen
 button.manage=Verwalten
 button.addnew=Hinzuf\u00fcgen
 button.delete=Entfernen
 button.manage=Verwalten
+button.combine=Zusammenschlie\u00dfen
 
 # File types
 filetype.txt=TXT-Dateien
 
 # File types
 filetype.txt=TXT-Dateien
@@ -562,12 +626,14 @@ filetype.kmz=KMZ-Dateien
 filetype.gpx=GPX-Dateien
 filetype.pov=POV-Dateien
 filetype.svg=SVG-Dateien
 filetype.gpx=GPX-Dateien
 filetype.pov=POV-Dateien
 filetype.svg=SVG-Dateien
+filetype.png=PNG-Dateien
 filetype.audio=MP3-, OGG-, WAV-Dateien
 
 # Display components
 display.nodata=Keine Daten geladen
 display.noaltitudes=Track enth\u00e4lt keine H\u00f6henangaben
 display.notimestamps=Track enth\u00e4lt keine Zeitstempel
 filetype.audio=MP3-, OGG-, WAV-Dateien
 
 # Display components
 display.nodata=Keine Daten geladen
 display.noaltitudes=Track enth\u00e4lt keine H\u00f6henangaben
 display.notimestamps=Track enth\u00e4lt keine Zeitstempel
+display.novalues=Track enth\u00e4lt keine Daten f\u00fcr dieses Feld
 details.trackdetails=Details des Tracks
 details.notrack=Kein Track geladen
 details.track.points=Punkte
 details.trackdetails=Details des Tracks
 details.notrack=Kein Track geladen
 details.track.points=Punkte
@@ -636,18 +702,31 @@ units.metres=Meter
 units.metres.short=m
 units.kilometres=Kilometer
 units.kilometres.short=km
 units.metres.short=m
 units.kilometres=Kilometer
 units.kilometres.short=km
+units.kilometresperhour=km pro Stunde
 units.kilometresperhour.short=km/h
 units.kilometresperhour.short=km/h
+units.miles=Meilen
+units.miles.short=Mi
+units.milesperhour=Meilen pro Stunde
+units.milesperhour.short=mph
 units.nauticalmiles=Seemeilen
 units.nauticalmiles.short=sm
 units.nauticalmilesperhour.short=kn
 units.nauticalmiles=Seemeilen
 units.nauticalmiles.short=sm
 units.nauticalmilesperhour.short=kn
+units.metrespersec=Meter pro Sekunde
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=feet pro Sekunde
 units.feetpersec.short=ft/s
 units.hours=Std
 units.feetpersec.short=ft/s
 units.hours=Std
+units.minutes=Minuten
+units.seconds=Sekunden
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
 units.iso8601=ISO 8601
 
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=und
+logic.or=oder
+
 # External urls
 url.googlemaps=maps.google.de
 wikipedia.lang=de
 # External urls
 url.googlemaps=maps.google.de
 wikipedia.lang=de
@@ -716,7 +795,7 @@ error.undofailed.text=Operation konnte nicht r\u00fcckg\u00e4ngig gemacht werden
 error.function.noop.title=Funktion hat nichts bewirkt
 error.rearrange.noop=Die Neuanordnung der Punkte hatte keinen Effekt
 error.function.notavailable.title=Funktion nicht verf\u00fcgbar
 error.function.noop.title=Funktion hat nichts bewirkt
 error.rearrange.noop=Die Neuanordnung der Punkte hatte keinen Effekt
 error.function.notavailable.title=Funktion nicht verf\u00fcgbar
-error.function.nojava3d=Diese Funktion ben\u00f6tigt die Java3d-Library,\ndie bei Sun.com erh\u00e4ltlich ist.
+error.function.nojava3d=Diese Funktion ben\u00f6tigt die Java3d-Library.
 error.3d=Ein Fehler ist bei der 3D Darstellung aufgetreten
 error.readme.notfound=Liesmich-Datei nicht gefunden
 error.osmimage.dialogtitle=Laden von Karten-Bildern fehlgeschlagen
 error.3d=Ein Fehler ist bei der 3D Darstellung aufgetreten
 error.readme.notfound=Liesmich-Datei nicht gefunden
 error.osmimage.dialogtitle=Laden von Karten-Bildern fehlgeschlagen
@@ -733,3 +812,4 @@ error.cache.notthere=Der Ordner wurde nicht gefunden
 error.cache.empty=Der Ordner ist leer
 error.cache.cannotdelete=Es konnte keine Kacheln gel\u00f6scht werden
 error.interpolate.invalidparameter=Die Anzahl der Punkte muss zwischen 1 und 1000 liegen
 error.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.
index caa5bd6a17faadb83a67042c2f89dbdaa999929a..fa2931f6e38357b47e6f04e3c755d4cc115af185 100644 (file)
@@ -83,6 +83,7 @@ function.exportkml=KML exportier\u00e4
 function.exportgpx=GPX exportier\u00e4
 function.exportpov=POV exportier\u00e4
 function.exportsvg=SVG exportier\u00e4
 function.exportgpx=GPX exportier\u00e4
 function.exportpov=POV exportier\u00e4
 function.exportsvg=SVG exportier\u00e4
+function.exportimage=Bild exportier\u00e4
 function.editwaypointname=Waypoint Name editiere
 function.compress=Track komprimier\u00e4
 function.deleterange=Beriich l\u00f6sche
 function.editwaypointname=Waypoint Name editiere
 function.compress=Track komprimier\u00e4
 function.deleterange=Beriich l\u00f6sche
@@ -97,6 +98,8 @@ function.charts=Diagramme
 function.show3d=Dr\u00fc\u00fc-D Aasicht
 function.distances=Entf\u00e4rnige
 function.fullrangedetails=Zues\u00e4tzlichi Beriichinfos
 function.show3d=Dr\u00fc\u00fc-D Aasicht
 function.distances=Entf\u00e4rnige
 function.fullrangedetails=Zues\u00e4tzlichi Beriichinfos
+function.estimatetime=Ziit absch\u00e4tze
+function.learnestimationparams=Ziitparameter erlerne
 function.setmapbg=Karte Hintegrund setz\u00e4
 function.getgpsies=Gpsies Tracks hol\u00e4
 function.uploadgpsies=Date zum Gpsies uufalad\u00e4
 function.setmapbg=Karte Hintegrund setz\u00e4
 function.getgpsies=Gpsies Tracks hol\u00e4
 function.uploadgpsies=Date zum Gpsies uufalad\u00e4
@@ -116,7 +119,6 @@ function.removeaudio=Audiodatei entfern\u00e4
 function.correlateaudios=Audios korrelier\u00e4
 function.playaudio=Audiofile abspiel\u00e4
 function.stopaudio=Abspielen abbr\u00e4ch\u00e4
 function.correlateaudios=Audios korrelier\u00e4
 function.playaudio=Audiofile abspiel\u00e4
 function.stopaudio=Abspielen abbr\u00e4ch\u00e4
-function.setkmzimagesize=Bildligr\u00f6sse inem KMZ setz\u00e4
 function.setpaths=Programmepfade setz\u00e4
 function.setcolours=Farben setz\u00e4
 function.setlinewidth=Liniedicke setz\u00e4
 function.setpaths=Programmepfade setz\u00e4
 function.setcolours=Farben setz\u00e4
 function.setlinewidth=Liniedicke setz\u00e4
@@ -154,6 +156,10 @@ dialog.openoptions.deliminfo.records=Rekords, mit
 dialog.openoptions.deliminfo.fields=F\u00e4ldere
 dialog.openoptions.deliminfo.norecords=Kei Rekords
 dialog.openoptions.altitudeunits=H\u00f6chi Masseiheite
 dialog.openoptions.deliminfo.fields=F\u00e4ldere
 dialog.openoptions.deliminfo.norecords=Kei Rekords
 dialog.openoptions.altitudeunits=H\u00f6chi Masseiheite
+dialog.openoptions.speedunits=Masseiheite f\u00fcr Geschwindigkeite
+dialog.openoptions.vertspeedunits=Masseiheite f\u00fcr vertikale Geschwindigkeite
+dialog.openoptions.vspeed.positiveup=Positive bed\u00fc\u00fctet uufe
+dialog.openoptions.vspeed.positivedown=Positive bed\u00fc\u00fctet abe
 dialog.open.contentsdoubled=Dieses File h\u00e4t zwei Kopien von j\u00e4dem Punkt,\neimol als Waypoint und eimol als Trackpunkt.
 dialog.selecttracks.intro=W\u00e4hlet Sie die Tracks uus zum lad\u00e4
 dialog.selecttracks.noname=Unbenannt
 dialog.open.contentsdoubled=Dieses File h\u00e4t zwei Kopien von j\u00e4dem Punkt,\neimol als Waypoint und eimol als Trackpunkt.
 dialog.selecttracks.intro=W\u00e4hlet Sie die Tracks uus zum lad\u00e4
 dialog.selecttracks.noname=Unbenannt
@@ -172,6 +178,30 @@ dialog.gpssend.sendwaypoints=Waypoints schicke
 dialog.gpssend.sendtracks=Tracks schicke
 dialog.gpssend.trackname=Track Name
 dialog.saveoptions.title=File speicher\u00e4
 dialog.gpssend.sendtracks=Tracks schicke
 dialog.gpssend.trackname=Track Name
 dialog.saveoptions.title=File speicher\u00e4
+dialog.gpsbabel.filters=Filter
+dialog.addfilter.title=Filter inna\u00fce
+dialog.gpsbabel.filter.discard=W\u00e4gwerfe
+dialog.gpsbabel.filter.simplify=Vereifache
+dialog.gpsbabel.filter.distance=Distanz
+dialog.gpsbabel.filter.interpolate=Interpoliere
+dialog.gpsbabel.filter.discard.intro=P\u00fcnkte wegwerfen, im Fall
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Aazahl Satellite <
+dialog.gpsbabel.filter.discard.nofix=Punkt kei Fix h\u00e4t
+dialog.gpsbabel.filter.discard.unknownfix=Punkt unbekannti Fix h\u00e4t
+dialog.gpsbabel.filter.simplify.intro=P\u00fcnkte entf\u00e4rne bis
+dialog.gpsbabel.filter.simplify.maxpoints=Aazahl P\u00fcnkte <
+dialog.gpsbabel.filter.simplify.maxerror=oder F\u00e4hlerdistanz <
+dialog.gpsbabel.filter.simplify.crosstrack=Distanz quer
+dialog.gpsbabel.filter.simplify.length=L\u00e4ngediffer\u00e4nz
+dialog.gpsbabel.filter.simplify.relative=Relativ zum Hdop
+dialog.gpsbabel.filter.distance.intro=P\u00fcnkte entf\u00e4rne, die in der N\u00f6chi vo fr\u00fchere P\u00fcnkte sin
+dialog.gpsbabel.filter.distance.distance=im Fall Distanz <
+dialog.gpsbabel.filter.distance.time=und Ziitdiffer\u00e4nz <
+dialog.gpsbabel.filter.interpolate.intro=Zus\u00e4tzlichi P\u00fcnkte innat\u00fce
+dialog.gpsbabel.filter.interpolate.distance=im Fall Distanz >
+dialog.gpsbabel.filter.interpolate.time=oder Ziitdiffer\u00e4nz >
 dialog.save.fieldstosave=F\u00e4lder zu speicher\u00e4
 dialog.save.table.field=F\u00e4ld
 dialog.save.table.hasdata=Het Date
 dialog.save.fieldstosave=F\u00e4lder zu speicher\u00e4
 dialog.save.table.field=F\u00e4ld
 dialog.save.table.hasdata=Het Date
@@ -187,7 +217,10 @@ dialog.exportkml.text=Titel f\u00fcr die Date
 dialog.exportkml.altitude=Absolut H\u00f6chiinformation (f\u00fcrs Fliege)
 dialog.exportkml.kmz=Date ins kmz File komprimier\u00e4
 dialog.exportkml.exportimages=Bildli ins Kmz exportier\u00e4
 dialog.exportkml.altitude=Absolut H\u00f6chiinformation (f\u00fcrs Fliege)
 dialog.exportkml.kmz=Date ins kmz File komprimier\u00e4
 dialog.exportkml.exportimages=Bildli ins Kmz exportier\u00e4
+dialog.exportkml.imagesize=Bildligr\u00f6sse
 dialog.exportkml.trackcolour=Trackfarb
 dialog.exportkml.trackcolour=Trackfarb
+dialog.exportkml.standardkml=Standardes KML
+dialog.exportkml.extendedkml=Erwiitertes KML mit Ziitst\u00e4mple
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschriibig
 dialog.exportgpx.includetimestamps=Au Ziitst\u00e4mpel
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Beschriibig
 dialog.exportgpx.includetimestamps=Au Ziitst\u00e4mpel
@@ -203,11 +236,23 @@ dialog.exportpov.cameraz=Kamera Z
 dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Schtange
 dialog.exportpov.tubesandwalls=R\u00f6hre und W\u00e4nde
 dialog.exportpov.modelstyle=Modellstil
 dialog.exportpov.ballsandsticks=B\u00e4lle und Schtange
 dialog.exportpov.tubesandwalls=R\u00f6hre und W\u00e4nde
-dialog.exportpov.warningtracksize=Dieser Track h\u00e4t mega viele P\u00fcnkte, die Java3D villiicht n\u00f6d chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze?
+dialog.3d.warningtracksize=Dieser Track h\u00e4t mega viele P\u00fcnkte, die Java3D villiicht n\u00f6d chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze?
+dialog.exportpov.baseimage=Grundbild
+dialog.exportpov.cannotmakebaseimage=Bild chann n\u00f6d gspeicheret werde
+dialog.baseimage.title=Kartenbild
+dialog.baseimage.useimage=Bild verw\u00e4nde
+dialog.baseimage.mapsource=Kartequ\u00e4lle
+dialog.baseimage.zoom=Zoomstufe
+dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
+dialog.baseimage.tiles=Kachle
+dialog.baseimage.size=Bildgr\u00f6ssi
 dialog.exportsvg.text=W\u00e4hlet Sie die Parameter f\u00fcrs SVG Export uus
 dialog.exportsvg.phi=Richtigswinkel \u03D5
 dialog.exportsvg.theta=Neigigswinkel \u03B8
 dialog.exportsvg.gradients=Farbeverl\u00e4ufe verw\u00e4nde
 dialog.exportsvg.text=W\u00e4hlet Sie die Parameter f\u00fcrs SVG Export uus
 dialog.exportsvg.phi=Richtigswinkel \u03D5
 dialog.exportsvg.theta=Neigigswinkel \u03B8
 dialog.exportsvg.gradients=Farbeverl\u00e4ufe verw\u00e4nde
+dialog.exportimage.noimagepossible=Kartebilder m\u00fcsset scho gspeicheret werde, bevor sie bim Export verwendet werde k\u00f6nne
+dialog.exportimage.drawtrack=Track uf d Karte zeichne
+dialog.exportimage.textscalepercent=Text Skalierig (%)
 dialog.pointtype.desc=Folgende Punkttype speichere:
 dialog.pointtype.track=Trackp\u00fcnkte
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.desc=Folgende Punkttype speichere:
 dialog.pointtype.track=Trackp\u00fcnkte
 dialog.pointtype.waypoint=Waypoints
@@ -228,7 +273,9 @@ dialog.clearundo.title=Undo-Liste l\u00f6sch\u00e4
 dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste l\u00f6sche?\nAlle Undo Infos werdet verlore gah!
 dialog.pointedit.title=Punkt editier\u00e4
 dialog.pointedit.text=W\u00e4hlet Sie j\u00e4den F\u00e4ld uus zu editiere, und mitem 'Editier\u00e4' Chnopf den Wert \u00e4ndere
 dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste l\u00f6sche?\nAlle Undo Infos werdet verlore gah!
 dialog.pointedit.title=Punkt editier\u00e4
 dialog.pointedit.text=W\u00e4hlet Sie j\u00e4den F\u00e4ld uus zu editiere, und mitem 'Editier\u00e4' Chnopf den Wert \u00e4ndere
+dialog.pointedit.intro=W\u00e4hlet Sie j\u00e4den F\u00e4ld uus, um den Wert z'seh und z'\u00e4ndere
 dialog.pointedit.table.field=F\u00e4ld
 dialog.pointedit.table.field=F\u00e4ld
+dialog.pointedit.nofield=Kei F\u00e4ld uusgew\u00e4hlt
 dialog.pointedit.table.value=Wert
 dialog.pointedit.table.changed=Ge\u00e4ndert
 dialog.pointedit.changevalue.text=Gebet Sie den neuen Wert f\u00fcr diesen F\u00e4ld ina
 dialog.pointedit.table.value=Wert
 dialog.pointedit.table.changed=Ge\u00e4ndert
 dialog.pointedit.changevalue.text=Gebet Sie den neuen Wert f\u00fcr diesen F\u00e4ld ina
@@ -274,6 +321,27 @@ dialog.distances.toofewpoints=d'Funktion bruucht Waypoints um die Dischtanze z b
 dialog.fullrangedetails.intro=Hier sind die Infos vonem aktuelli Beriich
 dialog.fullrangedetails.coltotal=Inklusiv L\u00fccke
 dialog.fullrangedetails.colsegments=Ohni L\u00fccke
 dialog.fullrangedetails.intro=Hier sind die Infos vonem aktuelli Beriich
 dialog.fullrangedetails.coltotal=Inklusiv L\u00fccke
 dialog.fullrangedetails.colsegments=Ohni L\u00fccke
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Liecht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Uufstieg
+dialog.estimatetime.descent=Abstieg
+dialog.estimatetime.parameters=Parameter
+dialog.estimatetime.parameters.timefor=Ziit f\u00fcr
+dialog.estimatetime.results=Resultate
+dialog.estimatetime.results.estimatedtime=Abgesch\u00e4tzti Ziit
+dialog.estimatetime.results.actualtime=Gebruuchti Ziit
+dialog.estimatetime.error.nodistance=D Absch\u00e4tzige bruuchet zamegebundeti P\u00fcnkt mitene Distanz
+dialog.estimatetime.error.noaltitudes=D Beriich h\u00e4t kei H\u00f6hi Date
+dialog.learnestimationparams.intro=Hier sin die Parameter die usem Track uusgr\u00e4chnet worde sin
+dialog.learnestimationparams.averageerror=Fehler
+dialog.learnestimationparams.combine=Diese Parameter k\u00f6nnet miten aktuelli Werte z\u00e4megschlosse werde
+dialog.learnestimationparams.combinedresults=Z\u00e4megschlossene Resultate
+dialog.learnestimationparams.weight.100pccurrent=Aktuelli Werte behalte
+dialog.learnestimationparams.weight.current=aktuell
+dialog.learnestimationparams.weight.calculated=uusgr\u00e4chnet
+dialog.learnestimationparams.weight.50pc=Mittelwert zw\u00fcschet aktuelli und uusgr\u00e4chneti Werte
+dialog.learnestimationparams.weight.100pccalculated=Neui uusgr\u00e4chneti Werte \u00fcberneh
 dialog.setmapbg.intro=Eini von den Qu\u00e4llen uusw\u00e4hle, oder eini neui hinzuef\u00fcge
 dialog.addmapsource.title=Neui Kartequ\u00e4lle hinzuef\u00fcge
 dialog.addmapsource.sourcename=Sourcename
 dialog.setmapbg.intro=Eini von den Qu\u00e4llen uusw\u00e4hle, oder eini neui hinzuef\u00fcge
 dialog.addmapsource.title=Neui Kartequ\u00e4lle hinzuef\u00fcge
 dialog.addmapsource.sourcename=Sourcename
@@ -417,8 +485,7 @@ dialog.saveconfig.prune.exiftoolpath=Exiftool Pfad
 dialog.saveconfig.prune.mapsource=Kartenserver Index
 dialog.saveconfig.prune.mapsourcelist=Kartenservers
 dialog.saveconfig.prune.diskcache=Kartenordner
 dialog.saveconfig.prune.mapsource=Kartenserver Index
 dialog.saveconfig.prune.mapsourcelist=Kartenservers
 dialog.saveconfig.prune.diskcache=Kartenordner
-dialog.saveconfig.prune.kmzimagewidth=Bildbreiti im KMZ
-dialog.saveconfig.prune.kmzimageheight=Bildh\u00f6chi im KMZ
+dialog.saveconfig.prune.kmzimagewidth=Bildr\u00f6sse im KMZ
 dialog.saveconfig.prune.colourscheme=Farbeschema
 dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML Trackfarb
 dialog.saveconfig.prune.colourscheme=Farbeschema
 dialog.saveconfig.prune.linewidth=Liniedicke
 dialog.saveconfig.prune.kmltrackcolour=KML Trackfarb
@@ -473,9 +540,6 @@ dialog.searchwikipedianames.search=Sueche na:
 # 3d window
 dialog.3d.title=GpsPrune Dr\u00fc\u00fc-d Aasicht
 dialog.3d.altitudefactor=H\u00f6chivervilfachigsfaktor
 # 3d window
 dialog.3d.title=GpsPrune Dr\u00fc\u00fc-d Aasicht
 dialog.3d.altitudefactor=H\u00f6chivervilfachigsfaktor
-dialog.3dlines.title=GpsPrune Gitterlinie
-dialog.3dlines.empty=Kei Linie zum aazeig\u00e4!
-dialog.3dlines.intro=Hier sin die Linie f\u00fcr die dr\u00fc\u00fc-D Aasicht
 
 # Confirm messages
 confirm.loadfile=Date glade vom
 
 # Confirm messages
 confirm.loadfile=Date glade vom
@@ -525,7 +589,6 @@ button.cancel=Abbr\u00e4ch\u00e4
 button.overwrite=Ãœberschriib\u00e4
 button.moveup=Uuf\u00e4 schieb\u00e4
 button.movedown=Aba schieb\u00e4
 button.overwrite=Ãœberschriib\u00e4
 button.moveup=Uuf\u00e4 schieb\u00e4
 button.movedown=Aba schieb\u00e4
-button.showlines=Linie aazeig\u00e4
 button.edit=Editier\u00e4
 button.exit=Be\u00e4nd\u00e4
 button.close=Schliess\u00e4
 button.edit=Editier\u00e4
 button.exit=Be\u00e4nd\u00e4
 button.close=Schliess\u00e4
@@ -548,6 +611,7 @@ button.browse=Durasuech\u00e4...
 button.addnew=Hinzuef\u00fcg\u00e4
 button.delete=Entf\u00e4rn\u00e4
 button.manage=Verwolt\u00e4
 button.addnew=Hinzuef\u00fcg\u00e4
 button.delete=Entf\u00e4rn\u00e4
 button.manage=Verwolt\u00e4
+button.combine=Z\u00e4meschliess\u00e4
 
 # File types
 filetype.txt=TXT Dateie
 
 # File types
 filetype.txt=TXT Dateie
@@ -558,12 +622,14 @@ filetype.kmz=KMZ Dateie
 filetype.gpx=GPX Dateie
 filetype.pov=POV Dateie
 filetype.svg=SVG Dateie
 filetype.gpx=GPX Dateie
 filetype.pov=POV Dateie
 filetype.svg=SVG Dateie
+filetype.png=PNG Dateie
 filetype.audio=MP3, OGG, WAV Dateie
 
 # Display components
 display.nodata=Kei Date glade worde
 display.noaltitudes=Track h\u00e4t kei H\u00f6hi Date
 display.notimestamps=Track h\u00e4t kei Ziitst\u00e4mple
 filetype.audio=MP3, OGG, WAV Dateie
 
 # Display components
 display.nodata=Kei Date glade worde
 display.noaltitudes=Track h\u00e4t kei H\u00f6hi Date
 display.notimestamps=Track h\u00e4t kei Ziitst\u00e4mple
+display.novalues=Track h\u00e4t kei Date f\u00fcr s'F\u00e4ld
 details.trackdetails=Details vom Track
 details.notrack=Kei Track glade worde
 details.track.points=P\u00fcnkte
 details.trackdetails=Details vom Track
 details.notrack=Kei Track glade worde
 details.track.points=P\u00fcnkte
@@ -631,18 +697,31 @@ units.metres=Meter
 units.metres.short=m
 units.kilometres=Kilometer
 units.kilometres.short=km
 units.metres.short=m
 units.kilometres=Kilometer
 units.kilometres.short=km
+units.kilometresperhour=km pro Stund
 units.kilometresperhour.short=kmh
 units.kilometresperhour.short=kmh
+units.miles=Meile
+units.miles.short=Mi
+units.milesperhour=Meile pro Stund
+units.milesperhour.short=mph
 units.nauticalmiles=Seemeile
 units.nauticalmiles.short=sm
 units.nauticalmilesperhour.short=kn
 units.nauticalmiles=Seemeile
 units.nauticalmiles.short=sm
 units.nauticalmilesperhour.short=kn
+units.metrespersec=Meter pro Sekunde
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=feet pro Sekunde
 units.feetpersec.short=ft/s
 units.hours=Std
 units.feetpersec.short=ft/s
 units.hours=Std
+units.minutes=Minute
+units.seconds=Sekunde
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
 units.iso8601=ISO 8601
 
 units.degminsec=Grad-Min-Sek
 units.degmin=Grad-Min
 units.deg=Grad
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=und
+logic.or=oder
+
 # External urls
 url.googlemaps=maps.google.ch
 wikipedia.lang=als
 # External urls
 url.googlemaps=maps.google.ch
 wikipedia.lang=als
@@ -711,7 +790,7 @@ error.undofailed.text=Operation kann n\u00f6d r\u00fcckg\u00e4ngig gmacht werde
 error.function.noop.title=Funktion h\u00e4t gar n\u00fc\u00fct gmacht
 error.rearrange.noop=P\u00fcnkte Reorganisierig h\u00e4t kei Eff\u00e4kt gha
 error.function.notavailable.title=Funktion n\u00f6d verf\u00fcegbar
 error.function.noop.title=Funktion h\u00e4t gar n\u00fc\u00fct gmacht
 error.rearrange.noop=P\u00fcnkte Reorganisierig h\u00e4t kei Eff\u00e4kt gha
 error.function.notavailable.title=Funktion n\u00f6d verf\u00fcegbar
-error.function.nojava3d=Sorry, d'Funktion brucht d Java3d Library,\nvo Sun.com erh\u00e4ltlech.
+error.function.nojava3d=Sorry, d'Funktion brucht d Java3d Library.
 error.3d=N F\u00e4hler isch mitere 3d Darstellig ufgtr\u00e4te
 error.readme.notfound=L\u00e4s mi File n\u00f6d gfunde
 error.osmimage.dialogtitle=F\u00e4hle bim Bildli-Lade
 error.3d=N F\u00e4hler isch mitere 3d Darstellig ufgtr\u00e4te
 error.readme.notfound=L\u00e4s mi File n\u00f6d gfunde
 error.osmimage.dialogtitle=F\u00e4hle bim Bildli-Lade
@@ -728,3 +807,4 @@ error.cache.notthere=D Ordner isch n\u00f6d gfunde worde
 error.cache.empty=D Ordner h\u00e4t n\u00fc\u00fct drinne
 error.cache.cannotdelete=Es sin kei Kachle gl\u00f6scht worde
 error.interpolate.invalidparameter=D'Aazahl P\u00fcnkt muess zw\u00fcschet 1 und 1000 sii
 error.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.
index f2e70e1d9a6ba94f8a8c7d92dafa36985d9e3426..0193cb8c03f8d182dde38c64611705559ededa66 100644 (file)
@@ -84,6 +84,7 @@ function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
+function.exportimage=Export image
 function.editwaypointname=Edit waypoint name
 function.compress=Compress track
 function.deleterange=Delete range
 function.editwaypointname=Edit waypoint name
 function.compress=Compress track
 function.deleterange=Delete range
@@ -99,6 +100,8 @@ function.charts=Charts
 function.show3d=Three-D view
 function.distances=Distances
 function.fullrangedetails=Full range details
 function.show3d=Three-D view
 function.distances=Distances
 function.fullrangedetails=Full range details
+function.estimatetime=Estimate time
+function.learnestimationparams=Learn time estimation parameters
 function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
 function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
@@ -121,7 +124,6 @@ function.correlateaudios=Correlate audios
 function.playaudio=Play audio clip
 function.stopaudio=Stop audio clip
 function.setmapbg=Set map background
 function.playaudio=Play audio clip
 function.stopaudio=Stop audio clip
 function.setmapbg=Set map background
-function.setkmzimagesize=Set KMZ image size
 function.setpaths=Set program paths
 function.setcolours=Set colours
 function.setlinewidth=Set line width
 function.setpaths=Set program paths
 function.setcolours=Set colours
 function.setlinewidth=Set line width
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=records, with
 dialog.openoptions.deliminfo.fields=fields
 dialog.openoptions.deliminfo.norecords=No records
 dialog.openoptions.altitudeunits=Altitude units
 dialog.openoptions.deliminfo.fields=fields
 dialog.openoptions.deliminfo.norecords=No records
 dialog.openoptions.altitudeunits=Altitude units
+dialog.openoptions.speedunits=Speed units
+dialog.openoptions.vertspeedunits=Vertical speed units
+dialog.openoptions.vspeed.positiveup=Positive speeds upwards
+dialog.openoptions.vspeed.positivedown=Positive speeds downwards
 dialog.open.contentsdoubled=This file contains two copies of each point,\nonce as waypoints and once as track points.
 dialog.selecttracks.intro=Select the track or tracks to load
 dialog.selecttracks.noname=Unnamed
 dialog.open.contentsdoubled=This file contains two copies of each point,\nonce as waypoints and once as track points.
 dialog.selecttracks.intro=Select the track or tracks to load
 dialog.selecttracks.noname=Unnamed
@@ -176,6 +182,30 @@ dialog.gpsload.save=Save to file
 dialog.gpssend.sendwaypoints=Send waypoints
 dialog.gpssend.sendtracks=Send tracks
 dialog.gpssend.trackname=Track name
 dialog.gpssend.sendwaypoints=Send waypoints
 dialog.gpssend.sendtracks=Send tracks
 dialog.gpssend.trackname=Track name
+dialog.gpsbabel.filters=Filters
+dialog.addfilter.title=Add filter
+dialog.gpsbabel.filter.discard=Discard
+dialog.gpsbabel.filter.simplify=Simplify
+dialog.gpsbabel.filter.distance=Distance
+dialog.gpsbabel.filter.interpolate=Interpolate
+dialog.gpsbabel.filter.discard.intro=Discard points if
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Number of satellites <
+dialog.gpsbabel.filter.discard.nofix=Point has no fix
+dialog.gpsbabel.filter.discard.unknownfix=Point has unknown fix
+dialog.gpsbabel.filter.simplify.intro=Remove points until
+dialog.gpsbabel.filter.simplify.maxpoints=Number of points <
+dialog.gpsbabel.filter.simplify.maxerror=or error distance <
+dialog.gpsbabel.filter.simplify.crosstrack=cross-track
+dialog.gpsbabel.filter.simplify.length=length difference
+dialog.gpsbabel.filter.simplify.relative=relative to hdop
+dialog.gpsbabel.filter.distance.intro=Remove points if close to any previous point
+dialog.gpsbabel.filter.distance.distance=If distance <
+dialog.gpsbabel.filter.distance.time=and time difference <
+dialog.gpsbabel.filter.interpolate.intro=Add extra points between track points
+dialog.gpsbabel.filter.interpolate.distance=If distance >
+dialog.gpsbabel.filter.interpolate.time=or time difference >
 dialog.saveoptions.title=Save file
 dialog.save.fieldstosave=Fields to save
 dialog.save.table.field=Field
 dialog.saveoptions.title=Save file
 dialog.save.fieldstosave=Fields to save
 dialog.save.table.field=Field
@@ -192,7 +222,10 @@ dialog.exportkml.text=Title for the data
 dialog.exportkml.altitude=Absolute altitudes (for aviation)
 dialog.exportkml.kmz=Compress to make kmz file
 dialog.exportkml.exportimages=Export image thumbnails to kmz
 dialog.exportkml.altitude=Absolute altitudes (for aviation)
 dialog.exportkml.kmz=Compress to make kmz file
 dialog.exportkml.exportimages=Export image thumbnails to kmz
+dialog.exportkml.imagesize=Image size
 dialog.exportkml.trackcolour=Track colour
 dialog.exportkml.trackcolour=Track colour
+dialog.exportkml.standardkml=Standard KML
+dialog.exportkml.extendedkml=Extended KML with timestamps
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Description
 dialog.exportgpx.includetimestamps=Include timestamps
 dialog.exportgpx.name=Name
 dialog.exportgpx.desc=Description
 dialog.exportgpx.includetimestamps=Include timestamps
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Camera Z
 dialog.exportpov.modelstyle=Model style
 dialog.exportpov.ballsandsticks=Balls and sticks
 dialog.exportpov.tubesandwalls=Tubes and walls
 dialog.exportpov.modelstyle=Model style
 dialog.exportpov.ballsandsticks=Balls and sticks
 dialog.exportpov.tubesandwalls=Tubes and walls
-dialog.exportpov.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue?
+dialog.3d.warningtracksize=This track has a large number of points, which Java3D might not be able to display.\nAre you sure you want to continue?
+dialog.exportpov.baseimage=Base image
+dialog.exportpov.cannotmakebaseimage=Cannot write base image
+dialog.baseimage.title=Map image
+dialog.baseimage.useimage=Use image
+dialog.baseimage.mapsource=Map source
+dialog.baseimage.zoom=Zoom level
+dialog.baseimage.incomplete=Image incomplete
+dialog.baseimage.tiles=Tiles
+dialog.baseimage.size=Image size
 dialog.exportsvg.text=Select the parameters for the SVG export
 dialog.exportsvg.phi=Azimuth angle \u03D5
 dialog.exportsvg.theta=Elevation angle \u03B8
 dialog.exportsvg.gradients=Use gradients for shading
 dialog.exportsvg.text=Select the parameters for the SVG export
 dialog.exportsvg.phi=Azimuth angle \u03D5
 dialog.exportsvg.theta=Elevation angle \u03B8
 dialog.exportsvg.gradients=Use gradients for shading
+dialog.exportimage.noimagepossible=Map images need to be cached to disk in order to use them for an export.
+dialog.exportimage.drawtrack=Draw track on map
+dialog.exportimage.textscalepercent=Text scale factor (%)
 dialog.pointtype.desc=Save the following point types:
 dialog.pointtype.track=Track points
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.desc=Save the following point types:
 dialog.pointtype.track=Track points
 dialog.pointtype.waypoint=Waypoints
@@ -232,12 +277,14 @@ dialog.undo.none.text=No operations to undo!
 dialog.clearundo.title=Clear undo list
 dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost!
 dialog.pointedit.title=Edit point
 dialog.clearundo.title=Clear undo list
 dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost!
 dialog.pointedit.title=Edit point
-dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value
+dialog.pointedit.text=
+dialog.pointedit.intro=Select each field in turn to view and change the value
 dialog.pointedit.table.field=Field
 dialog.pointedit.table.field=Field
+dialog.pointedit.nofield=No field selected
 dialog.pointedit.table.value=Value
 dialog.pointedit.table.value=Value
-dialog.pointedit.table.changed=Changed
-dialog.pointedit.changevalue.text=Enter the new value for this field
-dialog.pointedit.changevalue.title=Edit field
+dialog.pointedit.table.changed=
+dialog.pointedit.changevalue.text=
+dialog.pointedit.changevalue.title=
 dialog.pointnameedit.name=Waypoint name
 dialog.pointnameedit.uppercase=UPPER case
 dialog.pointnameedit.lowercase=lower case
 dialog.pointnameedit.name=Waypoint name
 dialog.pointnameedit.uppercase=UPPER case
 dialog.pointnameedit.lowercase=lower case
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=This function needs waypoints in order to calculat
 dialog.fullrangedetails.intro=Here are the details for the selected range
 dialog.fullrangedetails.coltotal=Including gaps
 dialog.fullrangedetails.colsegments=Without gaps
 dialog.fullrangedetails.intro=Here are the details for the selected range
 dialog.fullrangedetails.coltotal=Including gaps
 dialog.fullrangedetails.colsegments=Without gaps
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Gentle
+dialog.estimatetime.steep=Steep
+dialog.estimatetime.climb=Climb
+dialog.estimatetime.descent=Descent
+dialog.estimatetime.parameters=Parameters
+dialog.estimatetime.parameters.timefor=Time for
+dialog.estimatetime.results=Results
+dialog.estimatetime.results.estimatedtime=Estimated time
+dialog.estimatetime.results.actualtime=Actual time
+dialog.estimatetime.error.nodistance=The time estimates need connected track points, to give a distance
+dialog.estimatetime.error.noaltitudes=The selection doesn't include any altitude information
+dialog.learnestimationparams.intro=These are the parameters calculated from this track
+dialog.learnestimationparams.averageerror=Average error
+dialog.learnestimationparams.combine=These parameters can be combined with the current values
+dialog.learnestimationparams.combinedresults=Combined results
+dialog.learnestimationparams.weight.100pccurrent=Keep current values
+dialog.learnestimationparams.weight.current=current
+dialog.learnestimationparams.weight.calculated=calculated
+dialog.learnestimationparams.weight.50pc=Average of current values and calculated ones
+dialog.learnestimationparams.weight.100pccalculated=Use new calculated values
 dialog.setmapbg.intro=Select one of the map sources, or add a new one
 dialog.addmapsource.title=Add new map source
 dialog.addmapsource.sourcename=Name of source
 dialog.setmapbg.intro=Select one of the map sources, or add a new one
 dialog.addmapsource.title=Add new map source
 dialog.addmapsource.sourcename=Name of source
@@ -422,8 +490,7 @@ dialog.saveconfig.prune.exiftoolpath=Path to exiftool
 dialog.saveconfig.prune.mapsource=Selected map source
 dialog.saveconfig.prune.mapsourcelist=Map sources
 dialog.saveconfig.prune.diskcache=Map cache
 dialog.saveconfig.prune.mapsource=Selected map source
 dialog.saveconfig.prune.mapsourcelist=Map sources
 dialog.saveconfig.prune.diskcache=Map cache
-dialog.saveconfig.prune.kmzimagewidth=KMZ image width
-dialog.saveconfig.prune.kmzimageheight=KMZ image height
+dialog.saveconfig.prune.kmzimagewidth=KMZ image size
 dialog.saveconfig.prune.colourscheme=Colour scheme
 dialog.saveconfig.prune.linewidth=Line width
 dialog.saveconfig.prune.kmltrackcolour=KML track colour
 dialog.saveconfig.prune.colourscheme=Colour scheme
 dialog.saveconfig.prune.linewidth=Line width
 dialog.saveconfig.prune.kmltrackcolour=KML track colour
@@ -478,9 +545,6 @@ dialog.searchwikipedianames.search=Search for:
 # 3d window
 dialog.3d.title=GpsPrune Three-d view
 dialog.3d.altitudefactor=Altitude exaggeration factor
 # 3d window
 dialog.3d.title=GpsPrune Three-d view
 dialog.3d.altitudefactor=Altitude exaggeration factor
-dialog.3dlines.title=GpsPrune gridlines
-dialog.3dlines.empty=No gridlines to display!
-dialog.3dlines.intro=These are the gridlines for the three-d view
 
 # Confirm messages
 confirm.loadfile=Data loaded from file
 
 # Confirm messages
 confirm.loadfile=Data loaded from file
@@ -529,7 +593,6 @@ button.cancel=Cancel
 button.overwrite=Overwrite
 button.moveup=Move up
 button.movedown=Move down
 button.overwrite=Overwrite
 button.moveup=Move up
 button.movedown=Move down
-button.showlines=Show lines
 button.edit=Edit
 button.exit=Exit
 button.close=Close
 button.edit=Edit
 button.exit=Exit
 button.close=Close
@@ -552,6 +615,7 @@ button.browse=Browse...
 button.addnew=Add new
 button.delete=Delete
 button.manage=Manage
 button.addnew=Add new
 button.delete=Delete
 button.manage=Manage
+button.combine=Combine
 
 # File types
 filetype.txt=TXT files
 
 # File types
 filetype.txt=TXT files
@@ -562,12 +626,14 @@ filetype.kmz=KMZ files
 filetype.gpx=GPX files
 filetype.pov=POV files
 filetype.svg=SVG files
 filetype.gpx=GPX files
 filetype.pov=POV files
 filetype.svg=SVG files
+filetype.png=PNG files
 filetype.audio=MP3, OGG, WAV files
 
 # Display components
 display.nodata=No data loaded
 display.noaltitudes=Track data does not include altitudes
 display.notimestamps=Track data does not include timestamps
 filetype.audio=MP3, OGG, WAV files
 
 # Display components
 display.nodata=No data loaded
 display.noaltitudes=Track data does not include altitudes
 display.notimestamps=Track data does not include timestamps
+display.novalues=Track data does not include values for this field
 details.trackdetails=Track details
 details.notrack=No track loaded
 details.track.points=Points
 details.trackdetails=Track details
 details.notrack=No track loaded
 details.track.points=Points
@@ -638,21 +704,31 @@ units.feet=Feet
 units.feet.short=ft
 units.kilometres=Kilometres
 units.kilometres.short=km
 units.feet.short=ft
 units.kilometres=Kilometres
 units.kilometres.short=km
+units.kilometresperhour=km per hour
 units.kilometresperhour.short=km/h
 units.miles=Miles
 units.miles.short=mi
 units.kilometresperhour.short=km/h
 units.miles=Miles
 units.miles.short=mi
+units.milesperhour=miles per hour
 units.milesperhour.short=mph
 units.nauticalmiles=Nautical miles
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=kts
 units.milesperhour.short=mph
 units.nauticalmiles=Nautical miles
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=kts
+units.metrespersec=metres per second
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=feet per second
 units.feetpersec.short=ft/s
 units.hours=hours
 units.feetpersec.short=ft/s
 units.hours=hours
+units.minutes=minutes
+units.seconds=seconds
 units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Degrees
 units.iso8601=ISO 8601
 
 units.degminsec=Deg-min-sec
 units.degmin=Deg-min
 units.deg=Degrees
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=and
+logic.or=or
+
 # External urls
 url.googlemaps=maps.google.co.uk
 wikipedia.lang=en
 # External urls
 url.googlemaps=maps.google.co.uk
 wikipedia.lang=en
@@ -721,7 +797,7 @@ error.undofailed.text=Failed to undo operation
 error.function.noop.title=Function had no effect
 error.rearrange.noop=Rearranging points had no effect
 error.function.notavailable.title=Function not available
 error.function.noop.title=Function had no effect
 error.rearrange.noop=Rearranging points had no effect
 error.function.notavailable.title=Function not available
-error.function.nojava3d=This function requires the Java3d library,\navailable from Sun.com.
+error.function.nojava3d=This function requires the Java3d library.
 error.3d=An error occurred with the 3d display
 error.readme.notfound=Readme file not found
 error.osmimage.dialogtitle=Error loading map images
 error.3d=An error occurred with the 3d display
 error.readme.notfound=Readme file not found
 error.osmimage.dialogtitle=Error loading map images
@@ -738,3 +814,4 @@ error.cache.notthere=The tile cache directory was not found
 error.cache.empty=The tile cache directory is empty
 error.cache.cannotdelete=No tiles could be deleted
 error.interpolate.invalidparameter=The number of points must be between 1 and 1000
 error.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.
index b2b2fc275285a3fe1c3bcb49e8b265d7cb4e4aa6..928d027dbc5180d6bd9b9ced150ed53aa0544eaf 100644 (file)
@@ -12,6 +12,7 @@ dialog.setcolours.intro=Click on a color patch to change the color
 # Measurement units
 units.metres=Meters
 units.kilometres=Kilometers
 # Measurement units
 units.metres=Meters
 units.kilometres=Kilometers
+units.metrespersec=meters per second
 
 # External urls
 url.googlemaps=maps.google.com
 
 # External urls
 url.googlemaps=maps.google.com
index 77f889328b5b00cbab166c52346afb72f1873181..2d2f3a5adf027984b8b34cfdc1119d9a52be5434 100644 (file)
@@ -97,7 +97,6 @@ function.show3d=Mostrar en 3-D
 function.distances=Distancias
 function.fullrangedetails=Detalles adicionales de rango
 function.setmapbg=Configurar fondo de mapa
 function.distances=Distancias
 function.fullrangedetails=Detalles adicionales de rango
 function.setmapbg=Configurar fondo de mapa
-function.setkmzimagesize=Configurar tama\u00f1os de las im\u00e1genes KMZ
 function.setpaths=Configurar rutas del programas
 function.getgpsies=Bajar ruta de Gpsies
 function.uploadgpsies=Subir recorrido a Gpsies
 function.setpaths=Configurar rutas del programas
 function.getgpsies=Bajar ruta de Gpsies
 function.uploadgpsies=Subir recorrido a Gpsies
@@ -188,6 +187,7 @@ dialog.exportkml.text=Descripci\u00f3n para los datos
 dialog.exportkml.altitude=Absoluta altitudes (para aviaci\u00f3n)
 dialog.exportkml.kmz=Comprimir al archivo kmz
 dialog.exportkml.exportimages=Exportar fotos al kmz
 dialog.exportkml.altitude=Absoluta altitudes (para aviaci\u00f3n)
 dialog.exportkml.kmz=Comprimir al archivo kmz
 dialog.exportkml.exportimages=Exportar fotos al kmz
+dialog.exportkml.imagesize=Tama\u00f1os de las im\u00e1genes
 dialog.exportkml.trackcolour=Color del track
 dialog.exportgpx.name=Nombre
 dialog.exportgpx.desc=Descripci\u00f3n
 dialog.exportkml.trackcolour=Color del track
 dialog.exportgpx.name=Nombre
 dialog.exportgpx.desc=Descripci\u00f3n
@@ -204,7 +204,7 @@ dialog.exportpov.cameraz=C\u00e1mara Z
 dialog.exportpov.modelstyle=Estilo
 dialog.exportpov.ballsandsticks=Balas en palos
 dialog.exportpov.tubesandwalls=Tubos y paredes
 dialog.exportpov.modelstyle=Estilo
 dialog.exportpov.ballsandsticks=Balas en palos
 dialog.exportpov.tubesandwalls=Tubos y paredes
-dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Est\u00e1 seguro de que desea continuar?
+dialog.3d.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Est\u00e1 seguro de que desea continuar?
 dialog.exportsvg.text=Seleccione los par\u00e1metros para exportar a SVG
 dialog.exportsvg.phi=\u00c1ngulo de azimuth \u03d5
 dialog.exportsvg.theta=\u00c1ngulo de elevaci\u00f3n
 dialog.exportsvg.text=Seleccione los par\u00e1metros para exportar a SVG
 dialog.exportsvg.phi=\u00c1ngulo de azimuth \u03d5
 dialog.exportsvg.theta=\u00c1ngulo de elevaci\u00f3n
@@ -465,9 +465,6 @@ dialog.searchwikipedianames.search=Buscar:
 # 3d window
 dialog.3d.title=GpsPrune vista 3-D
 dialog.3d.altitudefactor=Factor de exageraci\u00f3n de altura
 # 3d window
 dialog.3d.title=GpsPrune vista 3-D
 dialog.3d.altitudefactor=Factor de exageraci\u00f3n de altura
-dialog.3dlines.title=Cuadr\u00edcula GpsPrune
-dialog.3dlines.empty=¡No hay ninguna cuadr\u00edcula!
-dialog.3dlines.intro=Informaci\u00f3n de la cuadr\u00edcula
 
 # Confirm messages
 confirm.loadfile=Dato cargado de
 
 # Confirm messages
 confirm.loadfile=Dato cargado de
@@ -515,7 +512,6 @@ button.cancel=Cancelar
 button.overwrite=Sobreescribir
 button.moveup=Mover hacia arriba
 button.movedown=Mover hacia abajo
 button.overwrite=Sobreescribir
 button.moveup=Mover hacia arriba
 button.movedown=Mover hacia abajo
-button.showlines=Mostrar cuadr\u00edcula
 button.edit=Editar
 button.exit=Salir
 button.close=Cerrar
 button.edit=Editar
 button.exit=Salir
 button.close=Cerrar
@@ -701,7 +697,7 @@ error.undofailed.text=No ha sido posible deshacer la operaci\u00f3n
 error.function.noop.title=La funci\u00f3n no se ha efectuado
 error.rearrange.noop=Reordenaci\u00f3n de puntos no se ha efectuado
 error.function.notavailable.title=Funci\u00f3n no disponible
 error.function.noop.title=La funci\u00f3n no se ha efectuado
 error.rearrange.noop=Reordenaci\u00f3n de puntos no se ha efectuado
 error.function.notavailable.title=Funci\u00f3n no disponible
-error.function.nojava3d=Esta funci\u00f3n requiere la librer\u00eda Java3d,\ndisponible en Sun.com.
+error.function.nojava3d=Esta funci\u00f3n requiere la librer\u00eda Java3d.
 error.3d=Ha ocurrido un error con la funci\u00f3n 3-D
 error.readme.notfound=Archivo readme no encontrado
 error.osmimage.dialogtitle=Error al cargar el mapa
 error.3d=Ha ocurrido un error con la funci\u00f3n 3-D
 error.readme.notfound=Archivo readme no encontrado
 error.osmimage.dialogtitle=Error al cargar el mapa
index ab11b33f6089961693ed41c55e7ccb1b60625894..83045ea704db224eb00e9c9e877fbedd6baa423b 100644 (file)
@@ -100,7 +100,6 @@ function.show3d=Montrer en 3D
 function.distances=Distances
 function.fullrangedetails=Montrer tous les d\u00e9tails
 function.setmapbg=D\u00e9finir le fond de carte
 function.distances=Distances
 function.fullrangedetails=Montrer tous les d\u00e9tails
 function.setmapbg=D\u00e9finir le fond de carte
-function.setkmzimagesize=D\u00e9finir la taille de l'image KMZ
 function.setpaths=D\u00e9finir les chemins des programmes
 function.getgpsies=R\u00e9cup\u00e9rer les traces Gpsies
 function.uploadgpsies=T\u00e9l\u00e9charger la trace sur Gpsies
 function.setpaths=D\u00e9finir les chemins des programmes
 function.getgpsies=R\u00e9cup\u00e9rer les traces Gpsies
 function.uploadgpsies=T\u00e9l\u00e9charger la trace sur Gpsies
@@ -192,6 +191,7 @@ dialog.exportkml.text=Titre pour les donn\u00e9es
 dialog.exportkml.altitude=Altitudes absolues (pour l'aviation)
 dialog.exportkml.kmz=Compresser au format kmz
 dialog.exportkml.exportimages=Exporter les vignettes au format kmz
 dialog.exportkml.altitude=Altitudes absolues (pour l'aviation)
 dialog.exportkml.kmz=Compresser au format kmz
 dialog.exportkml.exportimages=Exporter les vignettes au format kmz
+dialog.exportkml.imagesize=Taille des images
 dialog.exportkml.trackcolour=Couleur de la trace
 dialog.exportgpx.name=Nom
 dialog.exportgpx.desc=L\u00e9gende
 dialog.exportkml.trackcolour=Couleur de la trace
 dialog.exportgpx.name=Nom
 dialog.exportgpx.desc=L\u00e9gende
@@ -208,7 +208,14 @@ dialog.exportpov.cameraz=Cam\u00e9ra Z
 dialog.exportpov.modelstyle=Style du mod\u00e8le
 dialog.exportpov.ballsandsticks=Points et b\u00e2tons
 dialog.exportpov.tubesandwalls=Tubes et murs
 dialog.exportpov.modelstyle=Style du mod\u00e8le
 dialog.exportpov.ballsandsticks=Points et b\u00e2tons
 dialog.exportpov.tubesandwalls=Tubes et murs
-dialog.exportpov.warningtracksize=Cette trace poss\u00e8de un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\n\u00cates-vous s\u00fbr de vouloir continuer ?
+dialog.3d.warningtracksize=Cette trace poss\u00e8de un grand nombre de points, Java3D peut ne pas pouvoir l'afficher.\n\u00cates-vous s\u00fbr de vouloir continuer ?
+dialog.baseimage.title=Image de la carte
+dialog.baseimage.useimage=Utiliser image
+dialog.baseimage.mapsource=Source de cartes
+dialog.baseimage.zoom=Zoom
+dialog.baseimage.incomplete=Image incompl\u00e8te
+dialog.baseimage.tiles=Tuiles
+dialog.baseimage.size=Taille de l'image
 dialog.exportsvg.text=S\u00e9lectionner les param\u00e8tres de l'export SVG
 dialog.exportsvg.phi=Angle d'azimuth \u03d5
 dialog.exportsvg.theta=Angle d'\u00e9l\u00e9vation \u03b8
 dialog.exportsvg.text=S\u00e9lectionner les param\u00e8tres de l'export SVG
 dialog.exportsvg.phi=Angle d'azimuth \u03d5
 dialog.exportsvg.theta=Angle d'\u00e9l\u00e9vation \u03b8
@@ -470,6 +477,7 @@ dialog.diskcache.deleteall=Efface toute les tuiles
 dialog.diskcache.deleted1=Effac\u00e9
 dialog.diskcache.deleted2=tuiles du cache
 dialog.deletefieldvalues.intro=Choisir le champ \u00e0 effacer pour l'\u00e9tendue actuelle
 dialog.diskcache.deleted1=Effac\u00e9
 dialog.diskcache.deleted2=tuiles du cache
 dialog.deletefieldvalues.intro=Choisir le champ \u00e0 effacer pour l'\u00e9tendue actuelle
+dialog.deletefieldvalues.nofields=L'\u00e9tendue actuelle n'a pas de champs \u00e0 effacer
 dialog.setlinewidth.text=Entrer l'\u00e9paisseur des lignes des traces (1-4)
 dialog.downloadosm.desc=Confirmer le t\u00e9l\u00e9chargement des donn\u00e9es OSM brutes pour la zone indiqu\u00e9e :
 dialog.searchwikipedianames.search=Chercher :
 dialog.setlinewidth.text=Entrer l'\u00e9paisseur des lignes des traces (1-4)
 dialog.downloadosm.desc=Confirmer le t\u00e9l\u00e9chargement des donn\u00e9es OSM brutes pour la zone indiqu\u00e9e :
 dialog.searchwikipedianames.search=Chercher :
@@ -477,9 +485,6 @@ dialog.searchwikipedianames.search=Chercher :
 # 3d window
 dialog.3d.title=Vue 3D de GpsPrune
 dialog.3d.altitudefactor=Facteur d'exag\u00e9ration de l'altitude
 # 3d window
 dialog.3d.title=Vue 3D de GpsPrune
 dialog.3d.altitudefactor=Facteur d'exag\u00e9ration de l'altitude
-dialog.3dlines.title=Grille de GpsPrune
-dialog.3dlines.empty=Pas de grille \u00e0 afficher !
-dialog.3dlines.intro=Ceci est la grille pour la vue 3D
 
 # Confirm messages
 confirm.loadfile=Donn\u00e9es charg\u00e9es depuis le fichier
 
 # Confirm messages
 confirm.loadfile=Donn\u00e9es charg\u00e9es depuis le fichier
@@ -528,7 +533,6 @@ button.cancel=Annuler
 button.overwrite=\u00c9craser
 button.moveup=Monter
 button.movedown=Descendre
 button.overwrite=\u00c9craser
 button.moveup=Monter
 button.movedown=Descendre
-button.showlines=Montrer les lignes
 button.edit=\u00c9diter
 button.exit=Terminer
 button.close=Fermer
 button.edit=\u00c9diter
 button.exit=Terminer
 button.close=Fermer
@@ -720,7 +724,7 @@ error.undofailed.text=\u00c9chec de l'op\u00e9ration d'annulation
 error.function.noop.title=Fonction sans effet
 error.rearrange.noop=R\u00e9arrangement des points sans effet
 error.function.notavailable.title=Function non-disponible
 error.function.noop.title=Fonction sans effet
 error.rearrange.noop=R\u00e9arrangement des points sans effet
 error.function.notavailable.title=Function non-disponible
-error.function.nojava3d=Cette fonction n\u00e9cessite la librairie Java3d,\ndisponible sur Sun.com.
+error.function.nojava3d=Cette fonction n\u00e9cessite la librairie Java3d.
 error.3d=Un probl\u00e8me est survenu avec l'affichage 3D
 error.readme.notfound=Fichier Lisez-moi introuvable
 error.osmimage.dialogtitle=Erreur au chargement des portions de cartes
 error.3d=Un probl\u00e8me est survenu avec l'affichage 3D
 error.readme.notfound=Fichier Lisez-moi introuvable
 error.osmimage.dialogtitle=Erreur au chargement des portions de cartes
index 837f9d9decf02318d9d028089261b6e4612f684b..0764c74b578afbe683c83dc01ebbd0c43c3e948b 100644 (file)
@@ -97,7 +97,6 @@ function.show3d=3D n\u00e9zet
 function.distances=T\u00e1vols\u00e1gok
 function.fullrangedetails=Teljes tartom\u00e1ny r\u00e9szletei
 function.setmapbg=H\u00e1tt\u00e9rk\u00e9p be\u00e1ll\u00edt\u00e1sa
 function.distances=T\u00e1vols\u00e1gok
 function.fullrangedetails=Teljes tartom\u00e1ny r\u00e9szletei
 function.setmapbg=H\u00e1tt\u00e9rk\u00e9p be\u00e1ll\u00edt\u00e1sa
-function.setkmzimagesize=KMZ k\u00e9pm\u00e9ret be\u00e1ll\u00edt\u00e1sa
 function.setpaths=Program\u00fatvonalak be\u00e1ll\u00edt\u00e1sa
 function.getgpsies=Gpsies nyomvonalak let\u00f6lt\u00e9se
 function.uploadgpsies=Nyomvonal felt\u00f6lt\u00e9se Gpsiesra
 function.setpaths=Program\u00fatvonalak be\u00e1ll\u00edt\u00e1sa
 function.getgpsies=Gpsies nyomvonalak let\u00f6lt\u00e9se
 function.uploadgpsies=Nyomvonal felt\u00f6lt\u00e9se Gpsiesra
@@ -204,7 +203,7 @@ dialog.exportpov.cameraz=Z kamera
 dialog.exportpov.modelstyle=Modell st\u00edlusa
 dialog.exportpov.ballsandsticks=Goly\u00f3k \u00e9s botok
 dialog.exportpov.tubesandwalls=Cs\u00f6vek \u00e9s falak
 dialog.exportpov.modelstyle=Modell st\u00edlusa
 dialog.exportpov.ballsandsticks=Goly\u00f3k \u00e9s botok
 dialog.exportpov.tubesandwalls=Cs\u00f6vek \u00e9s falak
-dialog.exportpov.warningtracksize=Ez a nyomvonal nagy sz\u00e1m\u00fa pontot tartalmaz, amelyet a Java3D nem biztos, hogy meg tud jelen\u00edteni.\nBiztos benne, hogy folytatni szeretn\u00e9?
+dialog.3d.warningtracksize=Ez a nyomvonal nagy sz\u00e1m\u00fa pontot tartalmaz, amelyet a Java3D nem biztos, hogy meg tud jelen\u00edteni.\nBiztos benne, hogy folytatni szeretn\u00e9?
 dialog.exportsvg.text=Param\u00e9terek kiv\u00e1laszt\u00e1sa az SVG exporthoz
 dialog.exportsvg.phi=Ir\u00e1nysz\u00f6g \u03d5
 dialog.exportsvg.theta=Emel\u00e9s sz\u00f6ge \u03b8
 dialog.exportsvg.text=Param\u00e9terek kiv\u00e1laszt\u00e1sa az SVG exporthoz
 dialog.exportsvg.phi=Ir\u00e1nysz\u00f6g \u03d5
 dialog.exportsvg.theta=Emel\u00e9s sz\u00f6ge \u03b8
@@ -465,9 +464,6 @@ dialog.searchwikipedianames.search=Keres\u00e9s erre:
 # 3d window
 dialog.3d.title=GpsPrune 3D n\u00e9zet
 dialog.3d.altitudefactor=Magass\u00e1gi ny\u00fajt\u00e1si t\u00e9nyez\u0151
 # 3d window
 dialog.3d.title=GpsPrune 3D n\u00e9zet
 dialog.3d.altitudefactor=Magass\u00e1gi ny\u00fajt\u00e1si t\u00e9nyez\u0151
-dialog.3dlines.title=GpsPrune r\u00e1csvonalak
-dialog.3dlines.empty=Nincsenek megjelen\u00edthet\u0151 r\u00e1csvonalak!
-dialog.3dlines.intro=Ezek a r\u00e1csvonalak a 3D n\u00e9zethez
 
 # Confirm messages
 confirm.loadfile=Adatok f\u00e1jlb\u00f3l bet\u00f6ltve
 
 # Confirm messages
 confirm.loadfile=Adatok f\u00e1jlb\u00f3l bet\u00f6ltve
@@ -515,7 +511,6 @@ button.cancel=M\u00e9gse
 button.overwrite=Fel\u00fcl\u00edr\u00e1s
 button.moveup=Mozgat\u00e1s feljebb
 button.movedown=Mozgat\u00e1s lejjebb
 button.overwrite=Fel\u00fcl\u00edr\u00e1s
 button.moveup=Mozgat\u00e1s feljebb
 button.movedown=Mozgat\u00e1s lejjebb
-button.showlines=Sorok megjelen\u00edt\u00e9se
 button.edit=Szerkeszt\u00e9s
 button.exit=Kil\u00e9p\u00e9s
 button.close=Bez\u00e1r\u00e1s
 button.edit=Szerkeszt\u00e9s
 button.exit=Kil\u00e9p\u00e9s
 button.close=Bez\u00e1r\u00e1s
@@ -701,7 +696,7 @@ error.undofailed.text=A m\u0171velet visszavon\u00e1sa nem siker\u00fclt
 error.function.noop.title=A funkci\u00f3 nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
 error.rearrange.noop=A pontok \u00fajrarendez\u00e9se nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
 error.function.notavailable.title=A funkci\u00f3 nem \u00e9rhet\u0151 el
 error.function.noop.title=A funkci\u00f3 nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
 error.rearrange.noop=A pontok \u00fajrarendez\u00e9se nem eredm\u00e9nyezett v\u00e1ltoz\u00e1st
 error.function.notavailable.title=A funkci\u00f3 nem \u00e9rhet\u0151 el
-error.function.nojava3d=Ehhez a funkci\u00f3hoz a Java3d f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r sz\u00fcks\u00e9ges,\n amely a Sun.com webhelyr\u0151l \u00e9rhet\u0151 el.
+error.function.nojava3d=Ehhez a funkci\u00f3hoz a Java3d f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r sz\u00fcks\u00e9ges.
 error.3d=Hiba t\u00f6rt\u00e9nt a 3d megjelen\u00edt\u00e9ssel
 error.readme.notfound=Az olvassel f\u00e1jl nem tal\u00e1lhat\u00f3
 error.osmimage.dialogtitle=Hiba a t\u00e9rk\u00e9p bet\u00f6lt\u00e9sekor
 error.3d=Hiba t\u00f6rt\u00e9nt a 3d megjelen\u00edt\u00e9ssel
 error.readme.notfound=Az olvassel f\u00e1jl nem tal\u00e1lhat\u00f3
 error.osmimage.dialogtitle=Hiba a t\u00e9rk\u00e9p bet\u00f6lt\u00e9sekor
index 989110a6b4820a0fce93565e81bb02004b86aa96..07027a00f1bb31d6d822b16e4ead7c6d95efa83e 100644 (file)
@@ -100,7 +100,6 @@ function.show3d=Mostra in 3D
 function.distances=Mostra distanze
 function.fullrangedetails=Mostra dettagli
 function.setmapbg=Configura sfondo mappa
 function.distances=Mostra distanze
 function.fullrangedetails=Mostra dettagli
 function.setmapbg=Configura sfondo mappa
-function.setkmzimagesize=Configura dimensione immagine KMZ
 function.setpaths=Configura percorsi programmi
 function.getgpsies=Ottieni traccie da Gpsies
 function.uploadgpsies=Carica traccia su Gpsies
 function.setpaths=Configura percorsi programmi
 function.getgpsies=Ottieni traccie da Gpsies
 function.uploadgpsies=Carica traccia su Gpsies
@@ -208,7 +207,8 @@ dialog.exportpov.cameraz=Camera Z
 dialog.exportpov.modelstyle=Stile del modello
 dialog.exportpov.ballsandsticks=Palle e bacchette
 dialog.exportpov.tubesandwalls=Tubi e pareti
 dialog.exportpov.modelstyle=Stile del modello
 dialog.exportpov.ballsandsticks=Palle e bacchette
 dialog.exportpov.tubesandwalls=Tubi e pareti
-dialog.exportpov.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D potrebbe non essere in grado di visualizzarli.\nSei sicuro di voler continuare?
+dialog.3d.warningtracksize=Questa traccia ha un elevato numero di punti, e Java3D potrebbe non essere in grado di visualizzarli.\nSei sicuro di voler continuare?
+dialog.exportkml.imagesize=Dimensione immagine
 dialog.exportsvg.text=Seleziona i parametri per esportare in SVG
 dialog.exportsvg.phi=Angolo orizzontale \u03d5
 dialog.exportsvg.theta=Angolo di elevazione \u03b8
 dialog.exportsvg.text=Seleziona i parametri per esportare in SVG
 dialog.exportsvg.phi=Angolo orizzontale \u03d5
 dialog.exportsvg.theta=Angolo di elevazione \u03b8
@@ -470,6 +470,7 @@ dialog.diskcache.deleteall=Cancellare tutti tasselli
 dialog.diskcache.deleted1=Cancellati
 dialog.diskcache.deleted2=files dal cache
 dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
 dialog.diskcache.deleted1=Cancellati
 dialog.diskcache.deleted2=files dal cache
 dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
+dialog.deletefieldvalues.nofields=Nell'intervallo selezionato non ci sono campi da cancellare
 dialog.setlinewidth.text=Specifica il tratteggio delle linee per disegnare la traccia (1-4)
 dialog.downloadosm.desc=Conferma lo scarico dei dati raw OSM per l'area specificata:
 dialog.searchwikipedianames.search=Cerca per:
 dialog.setlinewidth.text=Specifica il tratteggio delle linee per disegnare la traccia (1-4)
 dialog.downloadosm.desc=Conferma lo scarico dei dati raw OSM per l'area specificata:
 dialog.searchwikipedianames.search=Cerca per:
@@ -477,9 +478,6 @@ dialog.searchwikipedianames.search=Cerca per:
 # 3d window
 dialog.3d.title=Visione GpsPrune in 3D
 dialog.3d.altitudefactor=Fattore di moltiplicazione della quota
 # 3d window
 dialog.3d.title=Visione GpsPrune in 3D
 dialog.3d.altitudefactor=Fattore di moltiplicazione della quota
-dialog.3dlines.title=Griglia di GpsPrune
-dialog.3dlines.empty=Nessuna griglia mostrata!
-dialog.3dlines.intro=Queste sono le linee della griglia per la visione 3D
 
 # Confirm messages
 confirm.loadfile=Dati caricati da file
 
 # Confirm messages
 confirm.loadfile=Dati caricati da file
@@ -528,7 +526,6 @@ button.cancel=Annulla
 button.overwrite=Sovrascrivi
 button.moveup=Sposta in alto
 button.movedown=Sposta in basso
 button.overwrite=Sovrascrivi
 button.moveup=Sposta in alto
 button.movedown=Sposta in basso
-button.showlines=Mostra linee
 button.edit=Modifica
 button.exit=Esci
 button.close=Chiudi
 button.edit=Modifica
 button.exit=Esci
 button.close=Chiudi
@@ -672,7 +669,6 @@ undo.removephoto=rimuovi foto
 undo.removeaudio=rimuovi riprese audio
 undo.deleterange=cancella l'intervallo
 undo.croptrack=taglia la traccia
 undo.removeaudio=rimuovi riprese audio
 undo.deleterange=cancella l'intervallo
 undo.croptrack=taglia la traccia
-undo.deletemarked=
 undo.insert=inserisci punti
 undo.reverse=inverti l'intervallo
 undo.mergetracksegments=unisci segmenti traccia
 undo.insert=inserisci punti
 undo.reverse=inverti l'intervallo
 undo.mergetracksegments=unisci segmenti traccia
@@ -720,7 +716,7 @@ error.undofailed.text=Impossibile annullare l'operazione
 error.function.noop.title=La funzione non ha avuto effetto
 error.rearrange.noop=La riorganizzazione dei punto non ha avuto effetto
 error.function.notavailable.title=Funzione non disponibile
 error.function.noop.title=La funzione non ha avuto effetto
 error.rearrange.noop=La riorganizzazione dei punto non ha avuto effetto
 error.function.notavailable.title=Funzione non disponibile
-error.function.nojava3d=Questa funzione richiede la libreria Java3d,\ndisponibile all'indirizzo Sun.com.
+error.function.nojava3d=Questa funzione richiede la libreria Java3d.
 error.3d=\u00c8 avvenuto un errore nella visualizzazione 3D
 error.readme.notfound=Non ho trovato il file Leggimi
 error.osmimage.dialogtitle=Errore nel caricamento nelle immagini della mappa
 error.3d=\u00c8 avvenuto un errore nella visualizzazione 3D
 error.readme.notfound=Non ho trovato il file Leggimi
 error.osmimage.dialogtitle=Errore nel caricamento nelle immagini della mappa
index 33b54c9f3679a49d41394b5a7d8c1309dced5592..9739f87cf220e8d157e32de8e55d5bd9592cb612 100644 (file)
@@ -54,6 +54,26 @@ menu.map.autopan=\u81ea\u52d5\u79fb\u52d5
 menu.map.showmap=\u5730\u56f3\u3092\u8868\u793a
 menu.map.showscalebar=\u7e2e\u5c3a\u8868\u793a
 
 menu.map.showmap=\u5730\u56f3\u3092\u8868\u793a
 menu.map.showscalebar=\u7e2e\u5c3a\u8868\u793a
 
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.track=T
+altkey.menu.range=R
+altkey.menu.point=P
+altkey.menu.view=V
+altkey.menu.photo=O
+altkey.menu.audio=A
+altkey.menu.settings=S
+altkey.menu.help=H
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=O
+shortcut.menu.file.load=L
+shortcut.menu.file.save=S
+shortcut.menu.track.undo=Z
+shortcut.menu.edit.compress=C
+shortcut.menu.range.all=A
+shortcut.menu.help.help=H
+
 # Functions
 function.open=\u30d5\u30a1\u30a4\u30eb\u3092\u958b\u304f
 function.importwithgpsbabel=GPSBabel\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u30a4\u30f3\u30dd\u30fc\u30c8
 # Functions
 function.open=\u30d5\u30a1\u30a4\u30eb\u3092\u958b\u304f
 function.importwithgpsbabel=GPSBabel\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u30a4\u30f3\u30dd\u30fc\u30c8
@@ -63,6 +83,7 @@ function.exportkml=KML\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportgpx=GPX\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportpov=POV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportsvg=SVG\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportgpx=GPX\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportpov=POV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportsvg=SVG\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
+function.exportimage=\u30a4\u30e1\u30fc\u30b8\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.editwaypointname=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u306e\u540d\u524d\u3092\u7de8\u96c6
 function.compress=\u30c8\u30e9\u30c3\u30af\u3092\u5727\u7e2e
 function.deleterange=\u7bc4\u56f2\u3092\u524a\u9664
 function.editwaypointname=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u306e\u540d\u524d\u3092\u7de8\u96c6
 function.compress=\u30c8\u30e9\u30c3\u30af\u3092\u5727\u7e2e
 function.deleterange=\u7bc4\u56f2\u3092\u524a\u9664
@@ -78,7 +99,6 @@ function.show3d=3-D\u30d3\u30e5\u30fc
 function.distances=\u8ddd\u96e2
 function.fullrangedetails=\u5168\u7bc4\u56f2\u8a73\u7d30
 function.setmapbg=\u80cc\u666f\u5730\u56f3
 function.distances=\u8ddd\u96e2
 function.fullrangedetails=\u5168\u7bc4\u56f2\u8a73\u7d30
 function.setmapbg=\u80cc\u666f\u5730\u56f3
-function.setkmzimagesize=KML \u30a4\u30e1\u30fc\u30b8\u30b5\u30a4\u30ba
 function.setpaths=\u5916\u90e8\u30d7\u30ed\u30b0\u30e9\u30e0\u30d1\u30b9\u3092\u8a2d\u5b9a
 function.getgpsies=Gpsies\u30c8\u30e9\u30c3\u30af\u3092\u5f97\u308b
 function.uploadgpsies=Gpsies\u306b\u30c8\u30e9\u30c3\u30af\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
 function.setpaths=\u5916\u90e8\u30d7\u30ed\u30b0\u30e9\u30e0\u30d1\u30b9\u3092\u8a2d\u5b9a
 function.getgpsies=Gpsies\u30c8\u30e9\u30c3\u30af\u3092\u5f97\u308b
 function.uploadgpsies=Gpsies\u306b\u30c8\u30e9\u30c3\u30af\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9
@@ -170,6 +190,7 @@ dialog.exportkml.altitude=\u7d76\u5bfe\u9ad8\u5ea6\uff08\u822a\u7a7a\u7528\uff09
 dialog.exportkml.kmz=KMZ\u30d5\u30a1\u30a4\u30eb\u3092\u5727\u7e2e
 dialog.exportkml.exportimages=KMZ\u3078\u753b\u50cf\u306e\u7e2e\u5c0f\u7248\u3092\u4fdd\u5b58
 dialog.exportkml.trackcolour=\u30c8\u30e9\u30c3\u30af\u306e\u8272
 dialog.exportkml.kmz=KMZ\u30d5\u30a1\u30a4\u30eb\u3092\u5727\u7e2e
 dialog.exportkml.exportimages=KMZ\u3078\u753b\u50cf\u306e\u7e2e\u5c0f\u7248\u3092\u4fdd\u5b58
 dialog.exportkml.trackcolour=\u30c8\u30e9\u30c3\u30af\u306e\u8272
+dialog.exportkml.standardkml=\u6a19\u6e96 KML
 dialog.exportgpx.name=\u540d\u524d
 dialog.exportgpx.desc=\u8a18\u8ff0
 dialog.exportgpx.includetimestamps=\u6642\u9593\u8a18\u9332\u3092\u542b\u3080
 dialog.exportgpx.name=\u540d\u524d
 dialog.exportgpx.desc=\u8a18\u8ff0
 dialog.exportgpx.includetimestamps=\u6642\u9593\u8a18\u9332\u3092\u542b\u3080
@@ -185,7 +206,7 @@ dialog.exportpov.cameraz=\u30ab\u30e1\u30e9 Z
 dialog.exportpov.modelstyle=\u30e2\u30c7\u30eb\u30b9\u30bf\u30a4\u30eb
 dialog.exportpov.ballsandsticks=\u7403\u3068\u68d2
 dialog.exportpov.tubesandwalls=\u7ba1\u3068\u58c1
 dialog.exportpov.modelstyle=\u30e2\u30c7\u30eb\u30b9\u30bf\u30a4\u30eb
 dialog.exportpov.ballsandsticks=\u7403\u3068\u68d2
 dialog.exportpov.tubesandwalls=\u7ba1\u3068\u58c1
-dialog.exportpov.warningtracksize=\u3053\u306e\u30c8\u30e9\u30c3\u30af\u306f\u975e\u5e38\u306b\u591a\u304f\u306e\u70b9\u304c\u3042\u308b\u306e\u3067\u3001Java3D \u3067\u306f\u8868\u793a\u3057\u5207\u308c\u306a\u3044\u3068\u601d\u308f\u308c\u307e\u3059\u3002\n\u7d9a\u3051\u307e\u3059\u304b\uff1f
+dialog.3d.warningtracksize=\u3053\u306e\u30c8\u30e9\u30c3\u30af\u306f\u975e\u5e38\u306b\u591a\u304f\u306e\u70b9\u304c\u3042\u308b\u306e\u3067\u3001Java3D \u3067\u306f\u8868\u793a\u3057\u5207\u308c\u306a\u3044\u3068\u601d\u308f\u308c\u307e\u3059\u3002\n\u7d9a\u3051\u307e\u3059\u304b\uff1f
 dialog.pointtype.desc=\u6b21\u306e\u70b9\u3092\u4fdd\u5b58\u3057\u307e\u3059\uff1a
 dialog.pointtype.track=\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8
 dialog.pointtype.waypoint=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8
 dialog.pointtype.desc=\u6b21\u306e\u70b9\u3092\u4fdd\u5b58\u3057\u307e\u3059\uff1a
 dialog.pointtype.track=\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8
 dialog.pointtype.waypoint=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8
@@ -407,9 +428,6 @@ dialog.searchwikipedianames.search=\u53f3\u8a18\u3092\u691c\u7d22:
 
 # 3d window
 dialog.3d.title=GpsPrune 3D \u8868\u793a
 
 # 3d window
 dialog.3d.title=GpsPrune 3D \u8868\u793a
-dialog.3dlines.title=GpsPrune \u683c\u5b50\u7dda
-dialog.3dlines.empty=\u683c\u5b50\u7dda\u304c\u8868\u793a\u3055\u308c\u307e\u305b\u3093
-dialog.3dlines.intro=\u3053\u308c\u3089\u304c 3D \u8868\u793a\u7528\u306e\u683c\u5b50\u7dda\u3067\u3059\u3002
 
 # Confirm messages
 confirm.loadfile=\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3080
 
 # Confirm messages
 confirm.loadfile=\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3080
@@ -457,7 +475,6 @@ button.cancel=\u53d6\u6d88
 button.overwrite=\u4e0a\u66f8\u304d
 button.moveup=\u4e0a\u3078
 button.movedown=\u4e0b\u3078
 button.overwrite=\u4e0a\u66f8\u304d
 button.moveup=\u4e0a\u3078
 button.movedown=\u4e0b\u3078
-button.showlines=\u7dda\u3092\u8868\u793a
 button.edit=\u7de8\u96c6
 button.exit=\u7d42\u4e86
 button.close=\u9589\u3058\u308b
 button.edit=\u7de8\u96c6
 button.exit=\u7d42\u4e86
 button.close=\u9589\u3058\u308b
@@ -489,6 +506,7 @@ filetype.kmz=KMZ\u30d5\u30a1\u30a4\u30eb
 filetype.gpx=GPX\u30d5\u30a1\u30a4\u30eb
 filetype.pov=POV\u30d5\u30a1\u30a4\u30eb
 filetype.svg=SVG\u30d5\u30a1\u30a4\u30eb
 filetype.gpx=GPX\u30d5\u30a1\u30a4\u30eb
 filetype.pov=POV\u30d5\u30a1\u30a4\u30eb
 filetype.svg=SVG\u30d5\u30a1\u30a4\u30eb
+filetype.png=PNG\u30d5\u30a1\u30a4\u30eb
 filetype.audio=MP3,OGG,WAV\u30d5\u30a1\u30a4\u30eb
 
 # Display components
 filetype.audio=MP3,OGG,WAV\u30d5\u30a1\u30a4\u30eb
 
 # Display components
@@ -569,6 +587,7 @@ units.milesperhour.short=mph
 units.metrespersec.short=m/s
 units.feetpersec.short=ft/s
 units.hours=\u6642\u9593
 units.metrespersec.short=m/s
 units.feetpersec.short=ft/s
 units.hours=\u6642\u9593
+units.seconds=\u79d2
 units.degminsec=\u5ea6-\u5206-\u79d2
 units.degmin=\u5ea6-\u5206
 units.deg=\u5ea6
 units.degminsec=\u5ea6-\u5206-\u79d2
 units.degmin=\u5ea6-\u5206
 units.deg=\u5ea6
index 68c5cd7910fe837cbfdba48a039cb9a7c9c6b6ea..b2b45964b3f3bdf261181f199ec861cb0bf12a08 100644 (file)
@@ -94,7 +94,6 @@ function.show3d=3\ucc28\uc6d0 \ubcf4\uae30
 function.distances=\uac70\ub9ac
 function.fullrangedetails=\uc5f0\uacb0\uc120 \uc0c1\uc138 \uc815\ubcf4 \ubcf4\uae30
 function.setmapbg=\ubc30\uacbd \uc9c0\ub3c4 \uc9c0\uc815
 function.distances=\uac70\ub9ac
 function.fullrangedetails=\uc5f0\uacb0\uc120 \uc0c1\uc138 \uc815\ubcf4 \ubcf4\uae30
 function.setmapbg=\ubc30\uacbd \uc9c0\ub3c4 \uc9c0\uc815
-function.setkmzimagesize=KMZ \uadf8\ub9bc \ud06c\uae30 \uc9c0\uc815
 function.setpaths=\uc678\ubd80\ud504\ub85c\uadf8\ub7a8 \uc9c0\uc815
 function.getgpsies=gpsies\uc5d0\uc11c \ud2b8\ub799\ubaa9\ub85d \uc5bb\uae30
 function.uploadgpsies=gpsies\ub85c \ud2b8\ub799 \uc62c\ub9ac\uae30
 function.setpaths=\uc678\ubd80\ud504\ub85c\uadf8\ub7a8 \uc9c0\uc815
 function.getgpsies=gpsies\uc5d0\uc11c \ud2b8\ub799\ubaa9\ub85d \uc5bb\uae30
 function.uploadgpsies=gpsies\ub85c \ud2b8\ub799 \uc62c\ub9ac\uae30
@@ -197,7 +196,7 @@ dialog.exportpov.cameraz=\uce74\uba54\ub77c\uc758 Z \uc88c\ud45c
 dialog.exportpov.modelstyle=\ubaa8\ub378 \uc2a4\ud0c0\uc77c
 dialog.exportpov.ballsandsticks=\ub9c9\ub300\uae30\uc640 \uacf5
 dialog.exportpov.tubesandwalls=\ubcbd\uacfc \ud29c\ube0c
 dialog.exportpov.modelstyle=\ubaa8\ub378 \uc2a4\ud0c0\uc77c
 dialog.exportpov.ballsandsticks=\ub9c9\ub300\uae30\uc640 \uacf5
 dialog.exportpov.tubesandwalls=\ubcbd\uacfc \ud29c\ube0c
-dialog.exportpov.warningtracksize=\uc774 \ud2b8\ub799\uc740 \uc9c0\uc810\uc774 \ub108\ubb34 \ub9ce\uc544 Java3D\uac00 \ud45c\ud604 \ubabb\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
+dialog.3d.warningtracksize=\uc774 \ud2b8\ub799\uc740 \uc9c0\uc810\uc774 \ub108\ubb34 \ub9ce\uc544 Java3D\uac00 \ud45c\ud604 \ubabb\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. /n \uadf8\ub798\ub3c4 \uacc4\uc18d \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?
 dialog.exportsvg.text=SVG\ub85c \ub0b4\ubcf4\ub0bc \ud30c\ub77c\ubbf8\ud130\ub97c \uc120\ud0dd\ud558\uc138\uc694
 dialog.exportsvg.phi=\ubc29\uc704\uac01(\u03d5)
 dialog.exportsvg.theta=\uace0\ub3c4(\u03b8)
 dialog.exportsvg.text=SVG\ub85c \ub0b4\ubcf4\ub0bc \ud30c\ub77c\ubbf8\ud130\ub97c \uc120\ud0dd\ud558\uc138\uc694
 dialog.exportsvg.phi=\ubc29\uc704\uac01(\u03d5)
 dialog.exportsvg.theta=\uace0\ub3c4(\u03b8)
@@ -442,9 +441,6 @@ dialog.searchwikipedianames.search=\ucc3e\uae30
 # 3d window
 dialog.3d.title=GpsPrune 3D \ubcf4\uae30
 dialog.3d.altitudefactor=\uace0\ub3c4 \uacfc\uc7a5 \uacc4\uc218
 # 3d window
 dialog.3d.title=GpsPrune 3D \ubcf4\uae30
 dialog.3d.altitudefactor=\uace0\ub3c4 \uacfc\uc7a5 \uacc4\uc218
-dialog.3dlines.title=GpsPrune \uaca9\uc790\uc120
-dialog.3dlines.empty=\uaca9\uc790\uc120 \uc5c6\uc774 \ud45c\uc2dc\ud558\uae30
-dialog.3dlines.intro=3D \ubcf4\uae30\ub97c \uc704\ud55c \uaca9\uc790\uc120\uc785\ub2c8\ub2e4.
 
 # Confirm messages
 confirm.loadfile=\ud30c\uc77c\uc5d0\uc11c \uc790\ub8cc\ub97c \ubd88\ub7ec\uc654\uc5b4\uc694.
 
 # Confirm messages
 confirm.loadfile=\ud30c\uc77c\uc5d0\uc11c \uc790\ub8cc\ub97c \ubd88\ub7ec\uc654\uc5b4\uc694.
@@ -492,7 +488,6 @@ button.cancel=\ucde8\uc18c
 button.overwrite=\ub36e\uc5b4\uc4f0\uae30
 button.moveup=\uc704\ub85c
 button.movedown=\uc544\ub798\ub85c
 button.overwrite=\ub36e\uc5b4\uc4f0\uae30
 button.moveup=\uc704\ub85c
 button.movedown=\uc544\ub798\ub85c
-button.showlines=\uc120 \ubcf4\uae30
 button.edit=\uc218\uc815
 button.exit=\ub098\uac00\uae30
 button.close=\ub2eb\uae30
 button.edit=\uc218\uc815
 button.exit=\ub098\uac00\uae30
 button.close=\ub2eb\uae30
index 5a75c2aac89438ac98a6d2f8bcea647f4a44a4d9..12c9307fa4a5f485678a0c7c2bb4ec90086ac299 100644 (file)
@@ -84,6 +84,7 @@ function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
 function.exportgpx=Export GPX
 function.exportpov=Export POV
 function.exportsvg=Export SVG
+function.exportimage=Bestand exporteren
 function.editwaypointname=Hernoem waypoint
 function.compress=Route comprimeren
 function.deleterange=Verwijder reeks
 function.editwaypointname=Hernoem waypoint
 function.compress=Route comprimeren
 function.deleterange=Verwijder reeks
@@ -99,8 +100,9 @@ function.charts=Diagram
 function.show3d=3D beeld
 function.distances=Afstanden
 function.fullrangedetails=Reeks details
 function.show3d=3D beeld
 function.distances=Afstanden
 function.fullrangedetails=Reeks details
+function.estimatetime=Geschatte tijd
+function.learnestimationparams=Parameters voor geschatte tijd
 function.setmapbg=Instellen kaart achtergrond
 function.setmapbg=Instellen kaart achtergrond
-function.setkmzimagesize=Instellen KMZ afbeelding grootte
 function.setpaths=Instellen programmapaden
 function.getgpsies=Routes van Gpsies ophalen
 function.uploadgpsies=Upload routes naar Gpsies
 function.setpaths=Instellen programmapaden
 function.getgpsies=Routes van Gpsies ophalen
 function.uploadgpsies=Upload routes naar Gpsies
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=bestanden, met
 dialog.openoptions.deliminfo.fields=velden
 dialog.openoptions.deliminfo.norecords=Geen bestanden
 dialog.openoptions.altitudeunits=Hoogte eenheden
 dialog.openoptions.deliminfo.fields=velden
 dialog.openoptions.deliminfo.norecords=Geen bestanden
 dialog.openoptions.altitudeunits=Hoogte eenheden
+dialog.openoptions.speedunits=Snelheid eenheden
+dialog.openoptions.vertspeedunits=Verticale snelheid eenheden
+dialog.openoptions.vspeed.positiveup=Positieve snelheid omhoog
+dialog.openoptions.vspeed.positivedown=Positieve snelheid omlaag
 dialog.open.contentsdoubled=Dit bestand bevat twee kopie\u00ebn van ieder punt,\neen keer als waypoint en een keer als punt
 dialog.selecttracks.intro=Selecteer route of routes om te laden
 dialog.selecttracks.noname=Onbenoemd
 dialog.open.contentsdoubled=Dit bestand bevat twee kopie\u00ebn van ieder punt,\neen keer als waypoint en een keer als punt
 dialog.selecttracks.intro=Selecteer route of routes om te laden
 dialog.selecttracks.noname=Onbenoemd
@@ -176,6 +182,30 @@ dialog.gpsload.save=Opslaan naar bestand
 dialog.gpssend.sendwaypoints=Verstuur waypoint
 dialog.gpssend.sendtracks=Verstuur routes
 dialog.gpssend.trackname=Routenaam
 dialog.gpssend.sendwaypoints=Verstuur waypoint
 dialog.gpssend.sendtracks=Verstuur routes
 dialog.gpssend.trackname=Routenaam
+dialog.gpsbabel.filters=Filters
+dialog.addfilter.title=Filter toevoegen
+dialog.gpsbabel.filter.discard=Verwijderen
+dialog.gpsbabel.filter.simplify=Vereenvoudigen
+dialog.gpsbabel.filter.distance=Afstand
+dialog.gpsbabel.filter.interpolate=Interpoleren
+dialog.gpsbabel.filter.discard.intro=Verwijder punten indien
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop <
+dialog.gpsbabel.filter.discard.numsats=Aantal satellieten <
+dialog.gpsbabel.filter.discard.nofix=Punt heeft geen gps fix
+dialog.gpsbabel.filter.discard.unknownfix=Punt heeft onbekende gps fix
+dialog.gpsbabel.filter.simplify.intro=Verwijder punten tot
+dialog.gpsbabel.filter.simplify.maxpoints=Aantal punten <
+dialog.gpsbabel.filter.simplify.maxerror=of fout-afstand <
+dialog.gpsbabel.filter.simplify.crosstrack=cross-track
+dialog.gpsbabel.filter.simplify.length=lengteverschil
+dialog.gpsbabel.filter.simplify.relative=relatief aan hdop
+dialog.gpsbabel.filter.distance.intro=Verwijder punten indien nabij enig eerder punt
+dialog.gpsbabel.filter.distance.distance=Indien afstand <
+dialog.gpsbabel.filter.distance.time=en tijdsverschil <
+dialog.gpsbabel.filter.interpolate.intro=Voeg extra punten toe tussen route punten
+dialog.gpsbabel.filter.interpolate.distance=Indien afstand >
+dialog.gpsbabel.filter.interpolate.time=of tijdsverschil >
 dialog.saveoptions.title=Bestand opslaan
 dialog.save.fieldstosave=Velden op te slaan
 dialog.save.table.field=Veld
 dialog.saveoptions.title=Bestand opslaan
 dialog.save.fieldstosave=Velden op te slaan
 dialog.save.table.field=Veld
@@ -192,7 +222,10 @@ dialog.exportkml.text=Titel voor de data
 dialog.exportkml.altitude=Absolute hoogten (voor luchtvaart)
 dialog.exportkml.kmz=Comprimeren voor kmz bestand
 dialog.exportkml.exportimages=Exporteer thumbnails naar kzm
 dialog.exportkml.altitude=Absolute hoogten (voor luchtvaart)
 dialog.exportkml.kmz=Comprimeren voor kmz bestand
 dialog.exportkml.exportimages=Exporteer thumbnails naar kzm
+dialog.exportkml.imagesize=Afbeeldinggrootte
 dialog.exportkml.trackcolour=Routekleur
 dialog.exportkml.trackcolour=Routekleur
+dialog.exportkml.standardkml=Standaard KML
+dialog.exportkml.extendedkml=Uitgebreide KML met tijden
 dialog.exportgpx.name=Naam
 dialog.exportgpx.desc=Omschrijving
 dialog.exportgpx.includetimestamps=Tijden meenemen
 dialog.exportgpx.name=Naam
 dialog.exportgpx.desc=Omschrijving
 dialog.exportgpx.includetimestamps=Tijden meenemen
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=Camera Z
 dialog.exportpov.modelstyle=Model stijl
 dialog.exportpov.ballsandsticks=Balletjes en stokjes
 dialog.exportpov.tubesandwalls=Buizen en muren
 dialog.exportpov.modelstyle=Model stijl
 dialog.exportpov.ballsandsticks=Balletjes en stokjes
 dialog.exportpov.tubesandwalls=Buizen en muren
-dialog.exportpov.warningtracksize=Deze route heeft een groot aantal punten. Java3D kan deze mogelijk niet tonen.\nWeet u zeker dat u door wilt gaan?
+dialog.3d.warningtracksize=Deze route heeft een groot aantal punten. Java3D kan deze mogelijk niet tonen.\nWeet u zeker dat u door wilt gaan?
+dialog.exportpov.baseimage=Basisafbeelding
+dialog.exportpov.cannotmakebaseimage=Kan basisafbeelding niet opslaan
+dialog.baseimage.title=Basisafbeelding
+dialog.baseimage.useimage=Gebruik afbeelding
+dialog.baseimage.mapsource=Kaartbron
+dialog.baseimage.zoom=Zoom niveau
+dialog.baseimage.incomplete=Afbeelding onvolledig
+dialog.baseimage.tiles=Tegels
+dialog.baseimage.size=Afbeeldinggrootte
 dialog.exportsvg.text=Selecteer de camera hoeken voor SVG export
 dialog.exportsvg.phi=Azimut hoek \u03d5
 dialog.exportsvg.theta=Stijgingshoek \u03b8
 dialog.exportsvg.gradients=Gebruik gradaties voor schaduw
 dialog.exportsvg.text=Selecteer de camera hoeken voor SVG export
 dialog.exportsvg.phi=Azimut hoek \u03d5
 dialog.exportsvg.theta=Stijgingshoek \u03b8
 dialog.exportsvg.gradients=Gebruik gradaties voor schaduw
+dialog.exportimage.noimagepossible=Kaartafbeeldingen dienen gecached te worden naar disk om ze te kunnen exporteren.
+dialog.exportimage.drawtrack=Teken route op kaart
+dialog.exportimage.textscalepercent=Tekstschaal factor (%)
 dialog.pointtype.desc=Sla de volgende punttypen op:
 dialog.pointtype.track=Routepunten
 dialog.pointtype.waypoint=Waypoints
 dialog.pointtype.desc=Sla de volgende punttypen op:
 dialog.pointtype.track=Routepunten
 dialog.pointtype.waypoint=Waypoints
@@ -233,7 +278,9 @@ dialog.clearundo.title=Ongedaan-maken lijst wissen
 dialog.clearundo.text=Weet u zeker dat u de ongedaan-maken lijst wilt wissen?\nAlle informatie zal verloren gaan!
 dialog.pointedit.title=Wijzig punt
 dialog.pointedit.text=Selecteer ieder te wijzigen veld en gebruik de "Wijzigen" knop om de waarden aan te passen.
 dialog.clearundo.text=Weet u zeker dat u de ongedaan-maken lijst wilt wissen?\nAlle informatie zal verloren gaan!
 dialog.pointedit.title=Wijzig punt
 dialog.pointedit.text=Selecteer ieder te wijzigen veld en gebruik de "Wijzigen" knop om de waarden aan te passen.
+dialog.pointedit.intro=Selecteer telkens een veld om de waarde te zien en te wijzigen
 dialog.pointedit.table.field=Veld
 dialog.pointedit.table.field=Veld
+dialog.pointedit.nofield=Geen veld geselecteerd
 dialog.pointedit.table.value=Waarde
 dialog.pointedit.table.changed=Gewijzigd
 dialog.pointedit.changevalue.text=Geef de nieuwe waarde voor dit veld
 dialog.pointedit.table.value=Waarde
 dialog.pointedit.table.changed=Gewijzigd
 dialog.pointedit.changevalue.text=Geef de nieuwe waarde voor dit veld
@@ -279,6 +326,27 @@ dialog.distances.toofewpoints=Deze functie heeft waypoints nodig om de afstand e
 dialog.fullrangedetails.intro=Dit zijn de details van de geselecteerde reeks
 dialog.fullrangedetails.coltotal=Inclusief hiaten
 dialog.fullrangedetails.colsegments=Zonder hiaten
 dialog.fullrangedetails.intro=Dit zijn de details van de geselecteerde reeks
 dialog.fullrangedetails.coltotal=Inclusief hiaten
 dialog.fullrangedetails.colsegments=Zonder hiaten
+dialog.estimatetime.details=Details
+dialog.estimatetime.gentle=Licht
+dialog.estimatetime.steep=Steil
+dialog.estimatetime.climb=Klimmen
+dialog.estimatetime.descent=Afdaling
+dialog.estimatetime.parameters=Parameters
+dialog.estimatetime.parameters.timefor=Tijd voor
+dialog.estimatetime.results=Resultaten
+dialog.estimatetime.results.estimatedtime=Geschatte tijd
+dialog.estimatetime.results.actualtime=Daadwerkelijke tijd
+dialog.estimatetime.error.nodistance=Tijdschattingen hebben geconnecteerde routepunten nodig om een afstand te bepalen
+dialog.estimatetime.error.noaltitudes=Deze selectie bevat geen hoogteinformatie
+dialog.learnestimationparams.intro=Dit zijn de parameters berekend voor deze route
+dialog.learnestimationparams.averageerror=Gemiddelde fout
+dialog.learnestimationparams.combine=Deze parameters kunnen gecombineerd worden met de huidige waarden
+dialog.learnestimationparams.combinedresults=Gecombineerde resultaten
+dialog.learnestimationparams.weight.100pccurrent=Behoud huidige waarden
+dialog.learnestimationparams.weight.current=huidig
+dialog.learnestimationparams.weight.calculated=berekend
+dialog.learnestimationparams.weight.50pc=Gemiddelde van huidige en berekende waarden
+dialog.learnestimationparams.weight.100pccalculated=Gebruik nieuwe berekende waarden
 dialog.setmapbg.intro=Selecteer een kaart-bron, of voeg een nieuwe bron toe.
 dialog.addmapsource.title=Nieuwe kaart-bron toevoegen
 dialog.addmapsource.sourcename=Naam van de bron
 dialog.setmapbg.intro=Selecteer een kaart-bron, of voeg een nieuwe bron toe.
 dialog.addmapsource.title=Nieuwe kaart-bron toevoegen
 dialog.addmapsource.sourcename=Naam van de bron
@@ -478,9 +546,6 @@ dialog.searchwikipedianames.search=Zoeken naar:
 # 3d window
 dialog.3d.title=GpsPrune in 3D
 dialog.3d.altitudefactor=Hoogte overdrijvingsfactor
 # 3d window
 dialog.3d.title=GpsPrune in 3D
 dialog.3d.altitudefactor=Hoogte overdrijvingsfactor
-dialog.3dlines.title=GpsPrune raster
-dialog.3dlines.empty=Geen raster om af te beelden
-dialog.3dlines.intro=Dit is het raster voor 3D
 
 # Confirm messages
 confirm.loadfile=Data van schijf geladen
 
 # Confirm messages
 confirm.loadfile=Data van schijf geladen
@@ -529,7 +594,6 @@ button.cancel=Annuleren
 button.overwrite=Overschrijven
 button.moveup=Omhoog
 button.movedown=Omlaag
 button.overwrite=Overschrijven
 button.moveup=Omhoog
 button.movedown=Omlaag
-button.showlines=Toon raster
 button.edit=Wijzigen
 button.exit=Afsluiten
 button.close=Sluiten
 button.edit=Wijzigen
 button.exit=Afsluiten
 button.close=Sluiten
@@ -552,6 +616,7 @@ button.browse=Browse...
 button.addnew=Nieuwe toevoegen
 button.delete=Verwijderen
 button.manage=Beheer
 button.addnew=Nieuwe toevoegen
 button.delete=Verwijderen
 button.manage=Beheer
+button.combine=Samenvoegen
 
 # File types
 filetype.txt=TXT bestand
 
 # File types
 filetype.txt=TXT bestand
@@ -562,12 +627,14 @@ filetype.kmz=KMZ bestand
 filetype.gpx=GPX bestand
 filetype.pov=POV bestand
 filetype.svg=SVG bestand
 filetype.gpx=GPX bestand
 filetype.pov=POV bestand
 filetype.svg=SVG bestand
+filetype.png=PNG bestand
 filetype.audio=MP3, OGG, WAV bestanden
 
 # Display components
 display.nodata=Geen gegevens geladen
 display.noaltitudes=Route gegevens bevatten geen hoogte
 display.notimestamps=Route gegevens bevatten geen tijdinformatie
 filetype.audio=MP3, OGG, WAV bestanden
 
 # Display components
 display.nodata=Geen gegevens geladen
 display.noaltitudes=Route gegevens bevatten geen hoogte
 display.notimestamps=Route gegevens bevatten geen tijdinformatie
+display.novalues=Route gegevens bevatten geen waarden voor dit veld
 details.trackdetails=Route details
 details.notrack=Geen route geladen
 details.track.points=Punten
 details.trackdetails=Route details
 details.notrack=Geen route geladen
 details.track.points=Punten
@@ -638,21 +705,31 @@ units.feet=Voet
 units.feet.short=ft
 units.kilometres=Kilometers
 units.kilometres.short=km
 units.feet.short=ft
 units.kilometres=Kilometers
 units.kilometres.short=km
+units.kilometresperhour=km per uur
 units.kilometresperhour.short=km/u
 units.miles=Mijlen
 units.miles.short=mi
 units.kilometresperhour.short=km/u
 units.miles=Mijlen
 units.miles.short=mi
+units.milesperhour=mijlen per uur
 units.milesperhour.short=mph
 units.nauticalmiles=Nautische mijlen
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=Kn
 units.milesperhour.short=mph
 units.nauticalmiles=Nautische mijlen
 units.nauticalmiles.short=N.m.
 units.nauticalmilesperhour.short=Kn
+units.metrespersec=meters per seconde
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=feet per seconde
 units.feetpersec.short=ft/s
 units.hours=uren
 units.feetpersec.short=ft/s
 units.hours=uren
+units.minutes=minuten
+units.seconds=seconden
 units.degminsec=Grd-min-sec
 units.degmin=Grd-min
 units.deg=Graden
 units.iso8601=ISO 8601
 
 units.degminsec=Grd-min-sec
 units.degmin=Grd-min
 units.deg=Graden
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=en
+logic.or=of
+
 # External urls
 url.googlemaps=maps.google.nl
 wikipedia.lang=nl
 # External urls
 url.googlemaps=maps.google.nl
 wikipedia.lang=nl
@@ -721,7 +798,7 @@ error.undofailed.text=Kon actie niet terugdraaien
 error.function.noop.title=Functie had geen effect
 error.rearrange.noop=Herschikken van punten had geen effect
 error.function.notavailable.title=Functie niet beschikbaar
 error.function.noop.title=Functie had geen effect
 error.rearrange.noop=Herschikken van punten had geen effect
 error.function.notavailable.title=Functie niet beschikbaar
-error.function.nojava3d=Deze functie heeft Java3d nodig,\nverkrijgbaar bij sun.com.
+error.function.nojava3d=Deze functie heeft Java3d nodig.
 error.3d=Er is een fout opgetreden bij de 3d afbeelding
 error.readme.notfound=Leesmij bestand niet gevonden
 error.osmimage.dialogtitle=Fout bij inlezen kaart afbeeldingen
 error.3d=Er is een fout opgetreden bij de 3d afbeelding
 error.readme.notfound=Leesmij bestand niet gevonden
 error.osmimage.dialogtitle=Fout bij inlezen kaart afbeeldingen
@@ -738,3 +815,4 @@ error.cache.notthere=De tegelcache map niet gevonden
 error.cache.empty=De tegelcache map is leeg
 error.cache.cannotdelete=Er konden geen tegels verwijderd worden
 error.interpolate.invalidparameter=Aantal punten moet tussen 1 en 1000 liggen
 error.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.
index 2ff8980250c2d5c598dffccdc1453ad91ed32fd9..7972f5940b8cadbac90aaa0a259bc6bc5abd3c48 100644 (file)
@@ -80,10 +80,11 @@ function.open=Otw\u00f3rz
 function.importwithgpsbabel=Importuj plik z GPSBabel
 function.loadfromgps=\u0141aduj z GPS
 function.sendtogps=Wy\u015blij dane do urz\u0105dzenia GPS
 function.importwithgpsbabel=Importuj plik z GPSBabel
 function.loadfromgps=\u0141aduj z GPS
 function.sendtogps=Wy\u015blij dane do urz\u0105dzenia GPS
-function.exportkml=Eksportuj KML
+function.exportkml=Eksportuj jako KML
 function.exportgpx=Eksportuj jako GPX
 function.exportpov=Eksportuj jako POV
 function.exportsvg=Eksportuj jako SVG
 function.exportgpx=Eksportuj jako GPX
 function.exportpov=Eksportuj jako POV
 function.exportsvg=Eksportuj jako SVG
+function.exportimage=Eksportuj jako obraz
 function.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015bredniego
 function.compress=Kompresuj \u015bcie\u017ck\u0119
 function.deleterange=Usu\u0144 zakres
 function.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015bredniego
 function.compress=Kompresuj \u015bcie\u017ck\u0119
 function.deleterange=Usu\u0144 zakres
@@ -99,8 +100,8 @@ function.charts=Wykres
 function.show3d=Poka\u017c model 3D
 function.distances=Odleg\u0142o\u015bci
 function.fullrangedetails=Wszystkie detale
 function.show3d=Poka\u017c model 3D
 function.distances=Odleg\u0142o\u015bci
 function.fullrangedetails=Wszystkie detale
+function.estimatetime=Przewidywany czas
 function.setmapbg=Wybierz map\u0119 t\u0142a
 function.setmapbg=Wybierz map\u0119 t\u0142a
-function.setkmzimagesize=Ustaw rozmiar zdj\u0119\u0107 w KMZ
 function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
 function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
 function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
 function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
 function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
 function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
@@ -159,6 +160,8 @@ dialog.openoptions.deliminfo.records=rekordy, z
 dialog.openoptions.deliminfo.fields=pola
 dialog.openoptions.deliminfo.norecords=Brak rekord\u00f3w
 dialog.openoptions.altitudeunits=Jednostki wysoko\u015bci
 dialog.openoptions.deliminfo.fields=pola
 dialog.openoptions.deliminfo.norecords=Brak rekord\u00f3w
 dialog.openoptions.altitudeunits=Jednostki wysoko\u015bci
+dialog.openoptions.speedunits=Jednostki pr\u0119dko\u015bci
+dialog.openoptions.vertspeedunits=Jednostki pr\u0119dko\u015bci pionowej
 dialog.open.contentsdoubled=Ten plik zawiera dwie kopie ka\u017cdego punktu.\nRaz jako punkt po\u015bredni, a raz jako punkt \u015bcie\u017cki.
 dialog.selecttracks.intro=Wybierz \u015bcie\u017ck\u0119 lub \u015bcie\u017cki
 dialog.selecttracks.noname=Nienazwane
 dialog.open.contentsdoubled=Ten plik zawiera dwie kopie ka\u017cdego punktu.\nRaz jako punkt po\u015bredni, a raz jako punkt \u015bcie\u017cki.
 dialog.selecttracks.intro=Wybierz \u015bcie\u017ck\u0119 lub \u015bcie\u017cki
 dialog.selecttracks.noname=Nienazwane
@@ -176,6 +179,30 @@ dialog.gpsload.save=Zapisz do pliku
 dialog.gpssend.sendwaypoints=Wy\u015blij punkty po\u015brednie
 dialog.gpssend.sendtracks=Wy\u015blij \u015bcie\u017cki
 dialog.gpssend.trackname=Nazwa \u015bcie\u017cki
 dialog.gpssend.sendwaypoints=Wy\u015blij punkty po\u015brednie
 dialog.gpssend.sendtracks=Wy\u015blij \u015bcie\u017cki
 dialog.gpssend.trackname=Nazwa \u015bcie\u017cki
+dialog.gpsbabel.filters=Filtry
+dialog.addfilter.title=Dodaj filtr
+dialog.gpsbabel.filter.discard=Odrzu\u0107
+dialog.gpsbabel.filter.simplify=Upro\u015b\u0107
+dialog.gpsbabel.filter.distance=Odleg\u0142o\u015b\u0107
+dialog.gpsbabel.filter.interpolate=Wstaw pomi\u0119dzy
+dialog.gpsbabel.filter.discard.intro=Odrzu\u0107 punkty je\u015bli
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Ilo\u015b\u0107 satelit\u00f3w <
+dialog.gpsbabel.filter.discard.nofix=Punkt bez fix-a
+dialog.gpsbabel.filter.discard.unknownfix=Punkt z nieznanym fix-em
+dialog.gpsbabel.filter.simplify.intro=Usuwaj punkty dop\u00f3ki
+dialog.gpsbabel.filter.simplify.maxpoints=Ilo\u015b\u0107 punkt\u00f3w <
+dialog.gpsbabel.filter.simplify.maxerror=lub b\u0142\u0105d odleg\u0142o\u015bci
+dialog.gpsbabel.filter.simplify.crosstrack=skrzy\u017cowane \u015bcie\u017cki
+dialog.gpsbabel.filter.simplify.length=d\u0142ugo\u015b\u0107 r\u00f3\u017cnicy
+dialog.gpsbabel.filter.simplify.relative=powi\u0105zan z Hdop
+dialog.gpsbabel.filter.distance.intro=Usu\u0144 punkty je\u015bli poprzednie punkt
+dialog.gpsbabel.filter.distance.distance=Je\u015bli odleg\u0142o\u015b\u0107 <
+dialog.gpsbabel.filter.distance.time=i r\u00f3\u017cnica w czasie <
+dialog.gpsbabel.filter.interpolate.intro=Dodaj ekstra punkty pomi\u0119dzy punktami \u015bcie\u017cki
+dialog.gpsbabel.filter.interpolate.distance=Je\u015bli odleg\u0142o\u015b\u0107 >
+dialog.gpsbabel.filter.interpolate.time=lub r\u00f3\u017cnica czasu >
 dialog.saveoptions.title=Zapisz plik
 dialog.save.fieldstosave=Pola do zapisu
 dialog.save.table.field=Pole
 dialog.saveoptions.title=Zapisz plik
 dialog.save.fieldstosave=Pola do zapisu
 dialog.save.table.field=Pole
@@ -192,7 +219,10 @@ dialog.exportkml.text=Tytu\u0142 dla danych
 dialog.exportkml.altitude=Do\u0142\u0105cz wysoko\u015bci (dla cel\u00f3w lotniczych)
 dialog.exportkml.kmz=Skompresuj do pliku KMZ
 dialog.exportkml.exportimages=Eksportuj miniaturki zdj\u0119\u0107 do KMZ
 dialog.exportkml.altitude=Do\u0142\u0105cz wysoko\u015bci (dla cel\u00f3w lotniczych)
 dialog.exportkml.kmz=Skompresuj do pliku KMZ
 dialog.exportkml.exportimages=Eksportuj miniaturki zdj\u0119\u0107 do KMZ
+dialog.exportkml.imagesize=Rozmiar zdj\u0119\u0107
 dialog.exportkml.trackcolour=Kolor \u015bcie\u017cki
 dialog.exportkml.trackcolour=Kolor \u015bcie\u017cki
+dialog.exportkml.standardkml=Standardowy KML
+dialog.exportkml.extendedkml=Standardowy KML ze znacznikami czasu (no, *extended*, not standard!)
 dialog.exportgpx.name=Nazwa
 dialog.exportgpx.desc=Opis
 dialog.exportgpx.includetimestamps=Do\u0142\u0105cz znaczniki czasu
 dialog.exportgpx.name=Nazwa
 dialog.exportgpx.desc=Opis
 dialog.exportgpx.includetimestamps=Do\u0142\u0105cz znaczniki czasu
@@ -208,11 +238,23 @@ dialog.exportpov.cameraz=Kamera Z
 dialog.exportpov.modelstyle=Styl modelu
 dialog.exportpov.ballsandsticks=Kule i pa\u0142ki
 dialog.exportpov.tubesandwalls=Rurki i \u015bciany
 dialog.exportpov.modelstyle=Styl modelu
 dialog.exportpov.ballsandsticks=Kule i pa\u0142ki
 dialog.exportpov.tubesandwalls=Rurki i \u015bciany
-dialog.exportpov.warningtracksize=Ta \u015bcie\u017cka ma bardzo wiele punkt\u00f3w, kt\u00f3rych Java3D mo\u017ce nie wy\u015bwietli\u0107.\nCzy chcesz kontynuowa\u0107?
+dialog.3d.warningtracksize=Ta \u015bcie\u017cka ma bardzo wiele punkt\u00f3w, kt\u00f3rych Java3D mo\u017ce nie wy\u015bwietli\u0107.\nCzy chcesz kontynuowa\u0107?
+dialog.exportpov.baseimage=Obraz podk\u0142adu
+dialog.exportpov.cannotmakebaseimage=Nie mo\u017cna zapisa\u0107 obrazu podk\u0142adu
+dialog.baseimage.title=Obrazu podk\u0142adu
+dialog.baseimage.useimage=U\u017cyj obrazu
+dialog.baseimage.mapsource=\u0179r\u00f3d\u0142o map
+dialog.baseimage.zoom=Poziom zbli\u017cenia
+dialog.baseimage.incomplete=Obraz niekompletny
+dialog.baseimage.tiles=Kafelki
+dialog.baseimage.size=Rozmiar obrazu
 dialog.exportsvg.text=Wybierz parametry eksportu do pliku SVG
 dialog.exportsvg.phi=azymut \u03d5
 dialog.exportsvg.theta=K\u0105t wzniesienia \u03b8
 dialog.exportsvg.gradients=U\u017cyj gradientu jako wype\u0142nienia
 dialog.exportsvg.text=Wybierz parametry eksportu do pliku SVG
 dialog.exportsvg.phi=azymut \u03d5
 dialog.exportsvg.theta=K\u0105t wzniesienia \u03b8
 dialog.exportsvg.gradients=U\u017cyj gradientu jako wype\u0142nienia
+dialog.exportimage.noimagepossible=Obrazy map musz\u0105 zosta\u0107 zapisane na dysku przed ich eksportem
+dialog.exportimage.drawtrack=Rysuj \u015bcie\u017ck\u0119 na mapie
+dialog.exportimage.textscalepercent=Wsp\u00f3\u0142czynnik skali tekstu (%)
 dialog.pointtype.desc=Zapisz punkty nast\u0119puj\u0105cych typ\u00f3w:
 dialog.pointtype.track=punkty \u015bcie\u017cki
 dialog.pointtype.waypoint=punkty po\u015brednie
 dialog.pointtype.desc=Zapisz punkty nast\u0119puj\u0105cych typ\u00f3w:
 dialog.pointtype.track=punkty \u015bcie\u017cki
 dialog.pointtype.waypoint=punkty po\u015brednie
@@ -233,7 +275,9 @@ dialog.clearundo.title=Wyczy\u015b\u0107 list\u0119 zmian
 dialog.clearundo.text=Czy na pewno chcesz wyczy\u015bci\u0107 list\u0119 zmian?\nWszystkie informacje o zmianach b\u0119d\u0105 utracone!
 dialog.pointedit.title=Edytuj punkt
 dialog.pointedit.text=Zaznacz wszystkie pola do edycji i u\u017cyj przycisku 'Edytuj' by zmieni\u0107 warto\u015bci
 dialog.clearundo.text=Czy na pewno chcesz wyczy\u015bci\u0107 list\u0119 zmian?\nWszystkie informacje o zmianach b\u0119d\u0105 utracone!
 dialog.pointedit.title=Edytuj punkt
 dialog.pointedit.text=Zaznacz wszystkie pola do edycji i u\u017cyj przycisku 'Edytuj' by zmieni\u0107 warto\u015bci
+dialog.pointedit.intro=Zaznacz wszystkie pola by zmieni\u0107 warto\u015bci
 dialog.pointedit.table.field=Pole
 dialog.pointedit.table.field=Pole
+dialog.pointedit.nofield=Nie wybrano \u017cadnego pola
 dialog.pointedit.table.value=Warto\u015b\u0107
 dialog.pointedit.table.changed=Zmieniony
 dialog.pointedit.changevalue.text=Wprowad\u017a now\u0105 warto\u015b\u0107 tego pola
 dialog.pointedit.table.value=Warto\u015b\u0107
 dialog.pointedit.table.changed=Zmieniony
 dialog.pointedit.changevalue.text=Wprowad\u017a now\u0105 warto\u015b\u0107 tego pola
@@ -279,6 +323,17 @@ dialog.distances.toofewpoints=Ta funkcja wymaga przynajmniej dw\u00f3ch punkt\u0
 dialog.fullrangedetails.intro=Szczeg\u00f3\u0142y wybranego zakresu
 dialog.fullrangedetails.coltotal=Z lukami
 dialog.fullrangedetails.colsegments=Bez luk
 dialog.fullrangedetails.intro=Szczeg\u00f3\u0142y wybranego zakresu
 dialog.fullrangedetails.coltotal=Z lukami
 dialog.fullrangedetails.colsegments=Bez luk
+dialog.estimatetime.details=Szczeg\u00f3\u0142y
+dialog.estimatetime.gentle=\u0141agodnie
+dialog.estimatetime.steep=Stromo
+dialog.estimatetime.parameters=Parametry
+dialog.estimatetime.parameters.timefor=Czas dla
+dialog.estimatetime.results=Wynik
+dialog.estimatetime.results.estimatedtime=Czas przewidywany
+dialog.estimatetime.results.actualtime=Czas bie\u017c\u0105cy
+dialog.learnestimationparams.averageerror=B\u0142\u0105d \u015bredni
+dialog.learnestimationparams.weight.current=bie\u017c\u0105ce
+dialog.learnestimationparams.weight.calculated=obliczone
 dialog.setmapbg.intro=Wybierz dostawc\u0119 map t\u0142a lub dodaj nowego
 dialog.addmapsource.title=Dodaj dostawc\u0119 map
 dialog.addmapsource.sourcename=Nazwa dostawcy
 dialog.setmapbg.intro=Wybierz dostawc\u0119 map t\u0142a lub dodaj nowego
 dialog.addmapsource.title=Dodaj dostawc\u0119 map
 dialog.addmapsource.sourcename=Nazwa dostawcy
@@ -478,9 +533,6 @@ dialog.searchwikipedianames.search=Szukaj
 # 3d window
 dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
 dialog.3d.altitudefactor=Wsp\u00f3\u0142czynnik skalowania wysoko\u015bci
 # 3d window
 dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
 dialog.3d.altitudefactor=Wsp\u00f3\u0142czynnik skalowania wysoko\u015bci
-dialog.3dlines.title=Linie siatki
-dialog.3dlines.empty=Brak siatki do wy\u015bwietlenia!
-dialog.3dlines.intro=Linie siatki w widoku 3D
 
 # Confirm messages
 confirm.loadfile=Za\u0142adowano dane z pliku
 
 # Confirm messages
 confirm.loadfile=Za\u0142adowano dane z pliku
@@ -529,7 +581,6 @@ button.cancel=Anuluj
 button.overwrite=Nadpisz
 button.moveup=Do g\u00f3ry
 button.movedown=Na d\u00f3\u0142
 button.overwrite=Nadpisz
 button.moveup=Do g\u00f3ry
 button.movedown=Na d\u00f3\u0142
-button.showlines=Poka\u017c linie
 button.edit=Edycja
 button.exit=Zako\u0144cz
 button.close=Zamknij
 button.edit=Edycja
 button.exit=Zako\u0144cz
 button.close=Zamknij
@@ -552,6 +603,7 @@ button.browse=Przegl\u0105daj...
 button.addnew=Dodaj nowy
 button.delete=Usu\u0144
 button.manage=Zarz\u0105dzaj
 button.addnew=Dodaj nowy
 button.delete=Usu\u0144
 button.manage=Zarz\u0105dzaj
+button.combine=Po\u0142\u0105cz
 
 # File types
 filetype.txt=Pliki TXT
 
 # File types
 filetype.txt=Pliki TXT
@@ -562,12 +614,14 @@ filetype.kmz=Pliki KMZ
 filetype.gpx=Pliki GPX
 filetype.pov=Pliki POV
 filetype.svg=Pliki SVG
 filetype.gpx=Pliki GPX
 filetype.pov=Pliki POV
 filetype.svg=Pliki SVG
+filetype.png=Pliki PNG
 filetype.audio=Pliki MP3, OGG, WAV
 
 # Display components
 display.nodata=Nie za\u0142adowano danych
 display.noaltitudes=\u015acie\u017cki nie zawieraj\u0105 informacji o wysoko\u015bci
 display.notimestamps=\u015acie\u017cki nie zawieraj\u0105 informacji o czasie
 filetype.audio=Pliki MP3, OGG, WAV
 
 # Display components
 display.nodata=Nie za\u0142adowano danych
 display.noaltitudes=\u015acie\u017cki nie zawieraj\u0105 informacji o wysoko\u015bci
 display.notimestamps=\u015acie\u017cki nie zawieraj\u0105 informacji o czasie
+display.novalues=\u015acie\u017cki nie zawieraj\u0105 warto\u015bci dla tego pola
 details.trackdetails=Szczeg\u00f3\u0142y \u015bcie\u017cki
 details.notrack=Brak za\u0142adowanych \u015bcie\u017cek
 details.track.points=Punkty
 details.trackdetails=Szczeg\u00f3\u0142y \u015bcie\u017cki
 details.notrack=Brak za\u0142adowanych \u015bcie\u017cek
 details.track.points=Punkty
@@ -638,21 +692,31 @@ units.feet=Stopy
 units.feet.short=ft
 units.kilometres=Kilometry
 units.kilometres.short=km
 units.feet.short=ft
 units.kilometres=Kilometry
 units.kilometres.short=km
+units.kilometresperhour=km na godzin\u0119
 units.kilometresperhour.short=km/h
 units.miles=Mile
 units.miles.short=mi
 units.kilometresperhour.short=km/h
 units.miles=Mile
 units.miles.short=mi
+units.milesperhour=mil na godzin\u0119
 units.milesperhour.short=mi/h
 units.nauticalmiles=Mile morskie
 units.nauticalmiles.short=Mm
 units.nauticalmilesperhour.short=w.
 units.milesperhour.short=mi/h
 units.nauticalmiles=Mile morskie
 units.nauticalmiles.short=Mm
 units.nauticalmilesperhour.short=w.
+units.metrespersec=metry (metr\u00f3w) na sekund\u0119
 units.metrespersec.short=m/s
 units.metrespersec.short=m/s
+units.feetpersec=stopy (st\u00f3p) na sekund\u0119
 units.feetpersec.short=ft/s
 units.hours=Godziny
 units.feetpersec.short=ft/s
 units.hours=Godziny
+units.minutes=minuty
+units.seconds=sekundy
 units.degminsec=Sto-min-sek
 units.degmin=Sto-min
 units.deg=Stopnie
 units.iso8601=ISO 8601
 
 units.degminsec=Sto-min-sek
 units.degmin=Sto-min
 units.deg=Stopnie
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=i
+logic.or=lub
+
 # External urls
 url.googlemaps=maps.google.pl
 wikipedia.lang=pl
 # External urls
 url.googlemaps=maps.google.pl
 wikipedia.lang=pl
@@ -721,7 +785,7 @@ error.undofailed.text=Nie mo\u017cna cofn\u0105\u0107
 error.function.noop.title=Funkcja nie ma skutku
 error.rearrange.noop=Przestawienie punkt\u00f3w nie przyniesie skutku
 error.function.notavailable.title=Funkcja nie dost\u0119pna
 error.function.noop.title=Funkcja nie ma skutku
 error.rearrange.noop=Przestawienie punkt\u00f3w nie przyniesie skutku
 error.function.notavailable.title=Funkcja nie dost\u0119pna
-error.function.nojava3d=Ta funkcja wymaga biblioteki Java3d,\ndost\u0119pnej na Sun.com.
+error.function.nojava3d=Ta funkcja wymaga biblioteki Java3d.
 error.3d=Nast\u0105pi\u0142 b\u0142\u0105d z wy\u015bwietlaniem 3D
 error.readme.notfound=Nie znaleziono pliku Readme
 error.osmimage.dialogtitle=B\u0142\u0105d przy \u0142adowaniu obraz\u00f3w map
 error.3d=Nast\u0105pi\u0142 b\u0142\u0105d z wy\u015bwietlaniem 3D
 error.readme.notfound=Nie znaleziono pliku Readme
 error.osmimage.dialogtitle=B\u0142\u0105d przy \u0142adowaniu obraz\u00f3w map
index 1220049fcfcdf17c7a8abe761f9e88c648ddac25..1d0e3efdc75a9ed5aac69727c4209a517ac5a4be 100644 (file)
@@ -97,7 +97,6 @@ function.show3d=Visualizar 3D
 function.distances=Dist\u00e2ncias
 function.fullrangedetails=Todos os detalhes
 function.setmapbg=Definir como fundo do mapa
 function.distances=Dist\u00e2ncias
 function.fullrangedetails=Todos os detalhes
 function.setmapbg=Definir como fundo do mapa
-function.setkmzimagesize=Definir tamanho da imagem KMZ
 function.setpaths=Definir caminhos do programa
 function.getgpsies=Obter rotas Gpsies
 function.uploadgpsies=Enviar rotas para o Gpsies
 function.setpaths=Definir caminhos do programa
 function.getgpsies=Obter rotas Gpsies
 function.uploadgpsies=Enviar rotas para o Gpsies
@@ -204,7 +203,8 @@ dialog.exportpov.cameraz=Z da C\u00e2mera
 dialog.exportpov.modelstyle=Estilo do modelo
 dialog.exportpov.ballsandsticks=Bolas e galhos
 dialog.exportpov.tubesandwalls=Tubos e muros
 dialog.exportpov.modelstyle=Estilo do modelo
 dialog.exportpov.ballsandsticks=Bolas e galhos
 dialog.exportpov.tubesandwalls=Tubos e muros
-dialog.exportpov.warningtracksize=Esta rota possui um grande n\u00famero de pontos, os quais o Java3D n\u00e3o ser\u00e1 capaz de exibir.\n Voc\u00ea tem certeza que deseja continuar?
+dialog.3d.warningtracksize=Esta rota possui um grande n\u00famero de pontos, os quais o Java3D n\u00e3o ser\u00e1 capaz de exibir.\n Voc\u00ea tem certeza que deseja continuar?
+dialog.exportkml.imagesize=Tamanho da imagem
 dialog.exportsvg.text=Selecione os par\u00e2metros para a exporta\u00e7\u00e3o para o SVG
 dialog.exportsvg.phi=\u00c2ngulo do azimute \u03d5
 dialog.exportsvg.theta=\u00c2ngulo da eleva\u00e7\u00e3o \u03b8
 dialog.exportsvg.text=Selecione os par\u00e2metros para a exporta\u00e7\u00e3o para o SVG
 dialog.exportsvg.phi=\u00c2ngulo do azimute \u03d5
 dialog.exportsvg.theta=\u00c2ngulo da eleva\u00e7\u00e3o \u03b8
@@ -465,9 +465,6 @@ dialog.searchwikipedianames.search=Procurar por:
 # 3d window
 dialog.3d.title=Vista 3D do GpsPrune
 dialog.3d.altitudefactor=Fator de exagera\u00e7\u00e3o de altitude
 # 3d window
 dialog.3d.title=Vista 3D do GpsPrune
 dialog.3d.altitudefactor=Fator de exagera\u00e7\u00e3o de altitude
-dialog.3dlines.title=Linhas da grade do GpsPrune
-dialog.3dlines.empty=Nenhuma linha de grade para exibir!
-dialog.3dlines.intro=Estas s\u00e3o as linhas da grade para a vista 3D.
 
 # Confirm messages
 confirm.loadfile=Dados carregados do arquivo
 
 # Confirm messages
 confirm.loadfile=Dados carregados do arquivo
@@ -515,7 +512,6 @@ button.cancel=Cancelar
 button.overwrite=Sobrescrever
 button.moveup=Mover acima
 button.movedown=Mover abaixo
 button.overwrite=Sobrescrever
 button.moveup=Mover acima
 button.movedown=Mover abaixo
-button.showlines=Mostrar linhas
 button.edit=Editar
 button.exit=Sair
 button.close=Fechar
 button.edit=Editar
 button.exit=Sair
 button.close=Fechar
@@ -702,7 +698,7 @@ error.undofailed.text=Falha para desfazer opera\u00e7\u00e3o
 error.function.noop.title=Fun\u00e7\u00e3o sem nenhum efeito
 error.rearrange.noop=Rearruma\u00e7\u00e3o de pontos n\u00e3o teve efeito
 error.function.notavailable.title=Fun\u00e7\u00e3o n\u00e3o dispon\u00edvel
 error.function.noop.title=Fun\u00e7\u00e3o sem nenhum efeito
 error.rearrange.noop=Rearruma\u00e7\u00e3o de pontos n\u00e3o teve efeito
 error.function.notavailable.title=Fun\u00e7\u00e3o n\u00e3o dispon\u00edvel
-error.function.nojava3d=Esta fun\u00e7\u00e3o precisa da biblioteca Java3d,\ndispon\u00edvel em Sun.com
+error.function.nojava3d=Esta fun\u00e7\u00e3o precisa da biblioteca Java3d
 error.3d=Um erro ocorreu com a exibi\u00e7\u00e3o 3D
 error.readme.notfound=Arquivo Leiame n\u00e3o encontrado
 error.osmimage.dialogtitle=Erro ao carregar imagens do mapa
 error.3d=Um erro ocorreu com a exibi\u00e7\u00e3o 3D
 error.readme.notfound=Arquivo Leiame n\u00e3o encontrado
 error.osmimage.dialogtitle=Erro ao carregar imagens do mapa
index c169538cc89af7250073129440ae76a7bd276bbd..fc851289a35f7ad4403d9451a9483ec5c8c69aea 100644 (file)
@@ -100,7 +100,6 @@ function.show3d=3D-\u0432\u0438\u0434
 function.distances=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f
 function.fullrangedetails=\u0414\u0435\u0442\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
 function.setmapbg=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0443-\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0443
 function.distances=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f
 function.fullrangedetails=\u0414\u0435\u0442\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0443
 function.setmapbg=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0443-\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0443
-function.setkmzimagesize=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 KMZ-\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
 function.setpaths=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0443\u0442\u0438 \u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430\u043c
 function.getgpsies=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a\u0438
 function.uploadgpsies=\u0412\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 gpsies.com
 function.setpaths=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0443\u0442\u0438 \u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430\u043c
 function.getgpsies=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a\u0438
 function.uploadgpsies=\u0412\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 gpsies.com
@@ -208,7 +207,7 @@ dialog.exportpov.cameraz=\u041a\u0430\u043c\u0435\u0440\u0430 Z
 dialog.exportpov.modelstyle=\u0421\u0442\u0438\u043b\u044c \u043c\u043e\u0434\u0435\u043b\u0438
 dialog.exportpov.ballsandsticks=\u041c\u044f\u0447\u0438 \u0438 \u043f\u0430\u043b\u043e\u0447\u043a\u0438
 dialog.exportpov.tubesandwalls=\u0422\u0440\u0443\u0431\u044b \u0438 \u0441\u0442\u0435\u043d\u044b
 dialog.exportpov.modelstyle=\u0421\u0442\u0438\u043b\u044c \u043c\u043e\u0434\u0435\u043b\u0438
 dialog.exportpov.ballsandsticks=\u041c\u044f\u0447\u0438 \u0438 \u043f\u0430\u043b\u043e\u0447\u043a\u0438
 dialog.exportpov.tubesandwalls=\u0422\u0440\u0443\u0431\u044b \u0438 \u0441\u0442\u0435\u043d\u044b
-dialog.exportpov.warningtracksize=\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u0442\u0440\u0435\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a - Java3D \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c!\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
+dialog.3d.warningtracksize=\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u0442\u0440\u0435\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a - Java3D \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u043d\u0435 \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c!\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
 dialog.exportsvg.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 SVG
 dialog.exportsvg.phi=\u0410\u0437\u0438\u043c\u0443\u0442 \u03d5
 dialog.exportsvg.theta=\u0423\u0433\u043e\u043b \u03b8
 dialog.exportsvg.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 SVG
 dialog.exportsvg.phi=\u0410\u0437\u0438\u043c\u0443\u0442 \u03d5
 dialog.exportsvg.theta=\u0423\u0433\u043e\u043b \u03b8
@@ -230,7 +229,7 @@ dialog.undo.pretext=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430
 dialog.undo.none.title=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
 dialog.undo.none.text=\u041d\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
 dialog.clearundo.title=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
 dialog.undo.none.title=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
 dialog.undo.none.text=\u041d\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
 dialog.clearundo.title=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b
-dialog.clearundo.text=\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b? n\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0438\u0449\u0435\u043d \u043d\u0430\u0432\u0441\u0435\u0433\u0434\u0430!
+dialog.clearundo.text=\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u043c\u0435\u043d\u044b?\n\u0421\u043f\u0438\u0441\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0438\u0449\u0435\u043d \u043d\u0430\u0432\u0441\u0435\u0433\u0434\u0430!
 dialog.pointedit.title=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443
 dialog.pointedit.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 Â«\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c» \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f
 dialog.pointedit.table.field=\u041f\u043e\u043b\u0435
 dialog.pointedit.title=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443
 dialog.pointedit.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 Â«\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c» \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f
 dialog.pointedit.table.field=\u041f\u043e\u043b\u0435
@@ -478,9 +477,6 @@ dialog.searchwikipedianames.search=\u041f\u043e\u0438\u0441\u043a \u0434\u043b\u
 # 3d window
 dialog.3d.title=GpsPrune 3D-\u0432\u0438\u0434
 dialog.3d.altitudefactor=\u041a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u043f\u043e \u0432\u044b\u0441\u043e\u0442\u0435
 # 3d window
 dialog.3d.title=GpsPrune 3D-\u0432\u0438\u0434
 dialog.3d.altitudefactor=\u041a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u043f\u043e \u0432\u044b\u0441\u043e\u0442\u0435
-dialog.3dlines.title=\u0421\u0435\u0442\u043a\u0430 GpsPrune
-dialog.3dlines.empty=\u041d\u0435\u0442 \u0441\u0435\u0442\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f!
-dialog.3dlines.intro=\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0441\u0435\u0442\u043a\u0438 3D-\u0432\u0438\u0434\u0430
 
 # Confirm messages
 confirm.loadfile=\u0414\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b
 
 # Confirm messages
 confirm.loadfile=\u0414\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b
@@ -529,7 +525,6 @@ button.cancel=\u041e\u0442\u043c\u0435\u043d\u0430
 button.overwrite=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c
 button.moveup=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u0432\u0435\u0440\u0445
 button.movedown=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u0437
 button.overwrite=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c
 button.moveup=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u0432\u0435\u0440\u0445
 button.movedown=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u0437
-button.showlines=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043b\u0438\u043d\u0438\u0438
 button.edit=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
 button.exit=\u0412\u044b\u0445\u043e\u0434
 button.close=\u0417\u0430\u043a\u0440\u044b\u0442\u044c
 button.edit=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
 button.exit=\u0412\u044b\u0445\u043e\u0434
 button.close=\u0417\u0430\u043a\u0440\u044b\u0442\u044c
@@ -562,6 +557,7 @@ filetype.kmz=KMZ \u0444\u0430\u0439\u043b\u044b
 filetype.gpx=GPX \u0444\u0430\u0439\u043b\u044b
 filetype.pov=POV \u0444\u0430\u0439\u043b\u044b
 filetype.svg=SVG \u0444\u0430\u0439\u043b\u044b
 filetype.gpx=GPX \u0444\u0430\u0439\u043b\u044b
 filetype.pov=POV \u0444\u0430\u0439\u043b\u044b
 filetype.svg=SVG \u0444\u0430\u0439\u043b\u044b
+filetype.png=PNG \u0444\u0430\u0439\u043b\u044b
 filetype.audio=MP3, OGG, WAV \u0444\u0430\u0439\u043b\u044b
 
 # Display components
 filetype.audio=MP3, OGG, WAV \u0444\u0430\u0439\u043b\u044b
 
 # Display components
index d0983667dbcb4621f7a8bb4613fa1fd8a44f888a..da661a9f8e37bd9136cb7e31547e661cf4a7384a 100644 (file)
@@ -87,7 +87,6 @@ function.show3d=3B g\u00fcr\u00fcnt\u00fcs\u00fc
 function.distances=Uzakl\u0131klar
 function.fullrangedetails=S\u0131ran\u0131n b\u00fct\u00fcn ayr\u0131nt\u0131lar
 function.setmapbg=Arkafonun haritas\u0131 se\u00e7
 function.distances=Uzakl\u0131klar
 function.fullrangedetails=S\u0131ran\u0131n b\u00fct\u00fcn ayr\u0131nt\u0131lar
 function.setmapbg=Arkafonun haritas\u0131 se\u00e7
-function.setkmzimagesize=KMZ resim boyutu ayarla
 function.setpaths=Uygulamalar\u0131n yollar\u0131 ayarla
 function.getgpsies=Gpsies.com'dan yolu al
 function.duplicatepoint=Noktay\u0131 kopyala
 function.setpaths=Uygulamalar\u0131n yollar\u0131 ayarla
 function.getgpsies=Gpsies.com'dan yolu al
 function.duplicatepoint=Noktay\u0131 kopyala
@@ -157,6 +156,7 @@ dialog.exportkml.text=Verinin ba\u015fl\u0131\u011f\u0131
 dialog.exportkml.altitude=Absolut y\u00fckseklikleri (u\u00e7u\u015f i\u00e7in)
 dialog.exportkml.kmz=kmz dosyas\u0131 olu\u015fturmak i\u00e7in s\u0131k\u0131\u015ft\u0131r
 dialog.exportkml.exportimages=Fotolar\u0131n t\u0131rnak resimleri kmz dosyada dahil et
 dialog.exportkml.altitude=Absolut y\u00fckseklikleri (u\u00e7u\u015f i\u00e7in)
 dialog.exportkml.kmz=kmz dosyas\u0131 olu\u015fturmak i\u00e7in s\u0131k\u0131\u015ft\u0131r
 dialog.exportkml.exportimages=Fotolar\u0131n t\u0131rnak resimleri kmz dosyada dahil et
+dialog.exportkml.imagesize=Resim boyutu
 dialog.exportkml.trackcolour=\u0130z rengi
 dialog.exportgpx.name=Ad\u0131
 dialog.exportgpx.desc=A\u00e7\u0131klama
 dialog.exportkml.trackcolour=\u0130z rengi
 dialog.exportgpx.name=Ad\u0131
 dialog.exportgpx.desc=A\u00e7\u0131klama
@@ -303,7 +303,6 @@ button.cancel=\u0130ptal
 button.overwrite=\u00dczerinde yaz
 button.moveup=Yukar\u0131
 button.movedown=A\u015fa\u011f\u0131
 button.overwrite=\u00dczerinde yaz
 button.moveup=Yukar\u0131
 button.movedown=A\u015fa\u011f\u0131
-button.showlines=Çizgiler g\u00f6r\u00fcnt\u00fcle
 button.edit=D\u00fczenle
 button.exit=Ç\u0131k\u0131\u015f
 button.close=Kapat
 button.edit=D\u00fczenle
 button.exit=Ç\u0131k\u0131\u015f
 button.close=Kapat
index 6068744d6f902cc5d8cb29c63142c3cb9120effd..d1936918b7b8fdd5f5d14d177e798d59cffcbd21 100644 (file)
@@ -84,6 +84,7 @@ function.exportkml=\u8f93\u51faKML\u6587\u4ef6
 function.exportgpx=\u8f93\u51faGPX\u6587\u4ef6
 function.exportpov=\u8f93\u51faPOV\u6587\u4ef6
 function.exportsvg=\u8f93\u51faSVG\u6587\u4ef6
 function.exportgpx=\u8f93\u51faGPX\u6587\u4ef6
 function.exportpov=\u8f93\u51faPOV\u6587\u4ef6
 function.exportsvg=\u8f93\u51faSVG\u6587\u4ef6
+function.exportimage=\u8f93\u51fa\u56fe\u50cf
 function.editwaypointname=\u7f16\u8f91\u822a\u70b9\u540d
 function.compress=\u538b\u7f29\u8f68\u8ff9(\u6807\u8bb0\u8981\u5220\u9664\u822a\u70b9)
 function.deleterange=\u5220\u9664\u8f68\u8ff9\u70b9\u6bb5
 function.editwaypointname=\u7f16\u8f91\u822a\u70b9\u540d
 function.compress=\u538b\u7f29\u8f68\u8ff9(\u6807\u8bb0\u8981\u5220\u9664\u822a\u70b9)
 function.deleterange=\u5220\u9664\u8f68\u8ff9\u70b9\u6bb5
@@ -92,15 +93,16 @@ function.interpolate=\u91cd\u53e0\u8f68\u8ff9\u70b9
 function.addtimeoffset=\u52a0\u5165\u65f6\u95f4\u5dee
 function.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
 function.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u8f6c\u4e3a\u65f6\u95f4
 function.addtimeoffset=\u52a0\u5165\u65f6\u95f4\u5dee
 function.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
 function.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u8f6c\u4e3a\u65f6\u95f4
-function.deletefieldvalues=\u5220\u9664\u533a\u57df\u6570\u503c
+function.deletefieldvalues=\u5220\u9664\u5b57\u6bb5\u503c
 function.findwaypoint=\u67e5\u627e\u822a\u70b9
 function.pastecoordinates=\u8f93\u5165\u65b0\u5750\u6807
 function.charts=\u56fe\u8868
 function.show3d=3D\u89c6\u56fe
 function.distances=\u8ddd\u79bb
 function.fullrangedetails=\u5168\u822a\u6bb5\u8be6\u7ec6\u4fe1\u606f
 function.findwaypoint=\u67e5\u627e\u822a\u70b9
 function.pastecoordinates=\u8f93\u5165\u65b0\u5750\u6807
 function.charts=\u56fe\u8868
 function.show3d=3D\u89c6\u56fe
 function.distances=\u8ddd\u79bb
 function.fullrangedetails=\u5168\u822a\u6bb5\u8be6\u7ec6\u4fe1\u606f
+function.estimatetime=\u4f30\u8ba1\u65f6\u95f4
+function.learnestimationparams=\u4f7f\u7528\u5f53\u524d\u8f68\u8ff9\u53c2\u6570\u4f30\u8ba1\u65f6\u95f4
 function.setmapbg=\u80cc\u666f\u5730\u56fe
 function.setmapbg=\u80cc\u666f\u5730\u56fe
-function.setkmzimagesize=\u8bbe\u7f6eKMZ\u56fe\u50cf\u5c3a\u5bf8
 function.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
 function.getgpsies=\u83b7\u53d6Gpsies\u8f68\u8ff9
 function.uploadgpsies=\u4e0a\u4f20\u8f68\u8ff9\u5230Gpsies
 function.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
 function.getgpsies=\u83b7\u53d6Gpsies\u8f68\u8ff9
 function.uploadgpsies=\u4e0a\u4f20\u8f68\u8ff9\u5230Gpsies
@@ -146,10 +148,10 @@ dialog.deletephoto.deletepoint=\u5220\u9664\u94fe\u63a5\u5230\u7167\u7247\u7684\
 dialog.deleteaudio.deletepoint=\u5220\u9664\u94fe\u63a5\u5230\u97f3\u9891\u7684\u8f68\u8ff9\u70b9\uff1f
 dialog.openoptions.title=\u6253\u5f00\u9009\u9879
 dialog.openoptions.filesnippet=\u63d0\u53d6\u6587\u4ef6\u7247\u6bb5
 dialog.deleteaudio.deletepoint=\u5220\u9664\u94fe\u63a5\u5230\u97f3\u9891\u7684\u8f68\u8ff9\u70b9\uff1f
 dialog.openoptions.title=\u6253\u5f00\u9009\u9879
 dialog.openoptions.filesnippet=\u63d0\u53d6\u6587\u4ef6\u7247\u6bb5
-dialog.load.table.field=\u6570\u636e\u6bb5
+dialog.load.table.field=\u5b57\u6bb5
 dialog.load.table.datatype=\u6570\u636e\u7c7b\u578b
 dialog.load.table.description=\u63cf\u8ff0
 dialog.load.table.datatype=\u6570\u636e\u7c7b\u578b
 dialog.load.table.description=\u63cf\u8ff0
-dialog.delimiter.label=\u6570\u636e\u6bb5\u5206\u9694\u7b26
+dialog.delimiter.label=\u5b57\u6bb5\u5206\u9694\u7b26
 dialog.delimiter.comma=\u9017\u53f7
 dialog.delimiter.tab=Tab
 dialog.delimiter.space=\u7a7a\u683c
 dialog.delimiter.comma=\u9017\u53f7
 dialog.delimiter.tab=Tab
 dialog.delimiter.space=\u7a7a\u683c
@@ -159,6 +161,10 @@ dialog.openoptions.deliminfo.records=\u6761\u8bb0\u5f55\uff0c
 dialog.openoptions.deliminfo.fields=\u6570\u636e\u6bb5
 dialog.openoptions.deliminfo.norecords=\u65e0\u7eaa\u5f55
 dialog.openoptions.altitudeunits=\u9ad8\u5ea6\u5355\u4f4d
 dialog.openoptions.deliminfo.fields=\u6570\u636e\u6bb5
 dialog.openoptions.deliminfo.norecords=\u65e0\u7eaa\u5f55
 dialog.openoptions.altitudeunits=\u9ad8\u5ea6\u5355\u4f4d
+dialog.openoptions.speedunits=\u901f\u5ea6\u5355\u4f4d
+dialog.openoptions.vertspeedunits=\u5782\u76f4\u901f\u5ea6\u5355\u4f4d
+dialog.openoptions.vspeed.positiveup=\u4e0a\u5347\u4e3a\u6b63
+dialog.openoptions.vspeed.positivedown=\u4e0b\u964d\u4e3a\u6b63
 dialog.open.contentsdoubled=\u6587\u4ef6\u542b\u6709\u4e24\u5957\u70b9\u4fe1\u606f\uff0c\u4e00\u5957\u822a\u70b9\u4fe1\u606f\u548c\u4e00\u5957\u8f68\u8ff9\u70b9\u4fe1\u606f
 dialog.selecttracks.intro=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u8f68\u8ff9
 dialog.selecttracks.noname=\u672a\u547d\u540d
 dialog.open.contentsdoubled=\u6587\u4ef6\u542b\u6709\u4e24\u5957\u70b9\u4fe1\u606f\uff0c\u4e00\u5957\u822a\u70b9\u4fe1\u606f\u548c\u4e00\u5957\u8f68\u8ff9\u70b9\u4fe1\u606f
 dialog.selecttracks.intro=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u8f68\u8ff9
 dialog.selecttracks.noname=\u672a\u547d\u540d
@@ -176,6 +182,30 @@ dialog.gpsload.save=\u4fdd\u5b58\u5230\u6587\u4ef6
 dialog.gpssend.sendwaypoints=\u53d1\u9001\u822a\u70b9
 dialog.gpssend.sendtracks=\u53d1\u9001\u8f68\u8ff9
 dialog.gpssend.trackname=\u8f68\u8ff9\u540d
 dialog.gpssend.sendwaypoints=\u53d1\u9001\u822a\u70b9
 dialog.gpssend.sendtracks=\u53d1\u9001\u8f68\u8ff9
 dialog.gpssend.trackname=\u8f68\u8ff9\u540d
+dialog.gpsbabel.filters=\u7b5b\u9009\u6761\u4ef6
+dialog.addfilter.title=\u6dfb\u52a0\u7b5b\u9009\u6761\u4ef6
+dialog.gpsbabel.filter.discard=\u820d\u5f03
+dialog.gpsbabel.filter.simplify=\u7b80\u5316
+dialog.gpsbabel.filter.distance=\u8ddd\u79bb
+dialog.gpsbabel.filter.interpolate=\u63d2\u503c
+dialog.gpsbabel.filter.discard.intro=\u820d\u5f03\u8f68\u8ff9\u70b9\u5f53
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=\u536b\u661f\u6570 <
+dialog.gpsbabel.filter.discard.nofix=\u8f68\u8ff9\u70b9\u672a\u5b9a\u4f4d
+dialog.gpsbabel.filter.discard.unknownfix=\u8f68\u8ff9\u70b9\u5b9a\u4f4d\u4e0d\u660e
+dialog.gpsbabel.filter.simplify.intro=\u5220\u9664\u6240\u6709\u8f68\u8ff9\u70b9\u76f4\u5230
+dialog.gpsbabel.filter.simplify.maxpoints=\u8f68\u8ff9\u70b9\u6570 <
+dialog.gpsbabel.filter.simplify.maxerror=\u6216\u8bef\u5dee\u8ddd\u79bb <
+dialog.gpsbabel.filter.simplify.crosstrack=\u504f\u822a
+dialog.gpsbabel.filter.simplify.length=\u957f\u5ea6\u5dee
+dialog.gpsbabel.filter.simplify.relative=\u76f8\u5bf9hdop
+dialog.gpsbabel.filter.distance.intro=\u79fb\u9664\u4e0e\u524d\u4e00\u70b9\u8ddd\u79bb\u592a\u8fd1\u7684\u8f68\u8ff9\u70b9
+dialog.gpsbabel.filter.distance.distance=\u5f53\u8ddd\u79bb <
+dialog.gpsbabel.filter.distance.time=\u4e14\u65f6\u95f4\u5dee <
+dialog.gpsbabel.filter.interpolate.intro=\u63d2\u5165\u4e2d\u95f4\u70b9
+dialog.gpsbabel.filter.interpolate.distance=\u5f53\u8ddd\u79bb >
+dialog.gpsbabel.filter.interpolate.time=\u6216\u65f6\u95f4\u5dee >
 dialog.saveoptions.title=\u4fdd\u5b58
 dialog.save.fieldstosave=\u4fdd\u5b58\u6570\u636e\u6bb5
 dialog.save.table.field=\u6570\u636e\u6bb5
 dialog.saveoptions.title=\u4fdd\u5b58
 dialog.save.fieldstosave=\u4fdd\u5b58\u6570\u636e\u6bb5
 dialog.save.table.field=\u6570\u636e\u6bb5
@@ -192,7 +222,10 @@ dialog.exportkml.text=\u6570\u636e\u540d\u79f0
 dialog.exportkml.altitude=\u7edd\u5bf9\u9ad8\u5ea6(\u822a\u7a7a\u7528)
 dialog.exportkml.kmz=\u538b\u7f29\u6210KMZ\u6587\u4ef6
 dialog.exportkml.exportimages=\u8f93\u51fa\u7f29\u7565\u56fe\u81f3KMZ
 dialog.exportkml.altitude=\u7edd\u5bf9\u9ad8\u5ea6(\u822a\u7a7a\u7528)
 dialog.exportkml.kmz=\u538b\u7f29\u6210KMZ\u6587\u4ef6
 dialog.exportkml.exportimages=\u8f93\u51fa\u7f29\u7565\u56fe\u81f3KMZ
+dialog.exportkml.imagesize=\u56fe\u50cf\u5c3a\u5bf8
 dialog.exportkml.trackcolour=\u8f68\u8ff9\u989c\u8272
 dialog.exportkml.trackcolour=\u8f68\u8ff9\u989c\u8272
+dialog.exportkml.standardkml=\u6807\u51c6KML
+dialog.exportkml.extendedkml=\u5e26\u65f6\u95f4\u6233\u8bb0\u7684\u6269\u5c55KML
 dialog.exportgpx.name=\u540d\u79f0
 dialog.exportgpx.desc=\u63cf\u8ff0
 dialog.exportgpx.includetimestamps=\u5305\u542b\u65f6\u95f4
 dialog.exportgpx.name=\u540d\u79f0
 dialog.exportgpx.desc=\u63cf\u8ff0
 dialog.exportgpx.includetimestamps=\u5305\u542b\u65f6\u95f4
@@ -208,11 +241,23 @@ dialog.exportpov.cameraz=\u76f8\u673aZ\u5750\u6807
 dialog.exportpov.modelstyle=\u6a21\u578b\u7c7b\u578b
 dialog.exportpov.ballsandsticks=\u7403\u6746\u6a21\u578b
 dialog.exportpov.tubesandwalls=\u7ba1\u5899\u6a21\u578b
 dialog.exportpov.modelstyle=\u6a21\u578b\u7c7b\u578b
 dialog.exportpov.ballsandsticks=\u7403\u6746\u6a21\u578b
 dialog.exportpov.tubesandwalls=\u7ba1\u5899\u6a21\u578b
-dialog.exportpov.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.3d.warningtracksize=\u8f68\u8ff9\u542b\u6709\u592a\u591a\u822a\u70b9\uff0cJAVA3D\u53ef\u80fd\u65e0\u6cd5\u663e\u793a\u3002\n\u662f\u5426\u7ee7\u7eed\uff1f
+dialog.exportpov.baseimage=\u57fa\u7840\u56fe
+dialog.exportpov.cannotmakebaseimage=\u65e0\u6cd5\u4fdd\u5b58\u57fa\u7840\u56fe
+dialog.baseimage.title=\u8bbe\u7f6e\u57fa\u7840\u56fe
+dialog.baseimage.useimage=\u4f7f\u7528\u6b64\u56fe\u50cf
+dialog.baseimage.mapsource=\u5730\u56fe\u6e90
+dialog.baseimage.zoom=\u7f29\u653e\u7ea7\u522b
+dialog.baseimage.incomplete=\u56fe\u50cf\u4e0d\u5b8c\u6574
+dialog.baseimage.tiles=\u5730\u56fe\u5757
+dialog.baseimage.size=\u56fe\u50cf\u5c3a\u5bf8
 dialog.exportsvg.text=\u9009\u62e9\u8f93\u51faSVG\u6587\u4ef6\u7684\u53c2\u6570
 dialog.exportsvg.phi=\u65b9\u4f4d\u89d2
 dialog.exportsvg.theta=\u4ef0\u89d2
 dialog.exportsvg.gradients=\u4f7f\u7528\u6e10\u53d8\u8272
 dialog.exportsvg.text=\u9009\u62e9\u8f93\u51faSVG\u6587\u4ef6\u7684\u53c2\u6570
 dialog.exportsvg.phi=\u65b9\u4f4d\u89d2
 dialog.exportsvg.theta=\u4ef0\u89d2
 dialog.exportsvg.gradients=\u4f7f\u7528\u6e10\u53d8\u8272
+dialog.exportimage.noimagepossible=\u8f93\u51fa\u7684\u5730\u56fe\u56fe\u50cf\u9996\u5148\u9700\u8981\u7f13\u5b58\u5728\u672c\u5730\u78c1\u76d8\u4e0a
+dialog.exportimage.drawtrack=\u7ed8\u51fa\u8f68\u8ff9
+dialog.exportimage.textscalepercent=\u6587\u5b57\u7f29\u653e\u6bd4\u4f8b (%)
 dialog.pointtype.desc=\u4fdd\u5b58\u4e0b\u5217\u70b9\uff1a
 dialog.pointtype.track=\u8f68\u8ff9\u70b9
 dialog.pointtype.waypoint=\u822a\u70b9
 dialog.pointtype.desc=\u4fdd\u5b58\u4e0b\u5217\u70b9\uff1a
 dialog.pointtype.track=\u8f68\u8ff9\u70b9
 dialog.pointtype.waypoint=\u822a\u70b9
@@ -232,12 +277,10 @@ dialog.undo.none.text=\u65e0\u64cd\u4f5c\u53ef\u64a4\u9500
 dialog.clearundo.title=\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355
 dialog.clearundo.text=\u662f\u5426\u786e\u5b9e\u8981\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355\uff1f\n\u64a4\u9500\u64cd\u4f5c\u4fe1\u606f\u4f1a\u4e22\u5931\uff01
 dialog.pointedit.title=\u7f16\u8f91\u8f68\u8ff9\u70b9
 dialog.clearundo.title=\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355
 dialog.clearundo.text=\u662f\u5426\u786e\u5b9e\u8981\u6e05\u9664\u64a4\u9500\u64cd\u4f5c\u6e05\u5355\uff1f\n\u64a4\u9500\u64cd\u4f5c\u4fe1\u606f\u4f1a\u4e22\u5931\uff01
 dialog.pointedit.title=\u7f16\u8f91\u8f68\u8ff9\u70b9
-dialog.pointedit.text=\u9009\u62e9\u8981\u7f16\u8f91\u7684\u533a\u57df\u5e76\u7528\u201c\u7f16\u8f91\u201d\u952e\u6539\u53d8\u6570\u503c
-dialog.pointedit.table.field=\u6570\u636e\u6bb5
+dialog.pointedit.intro=\u4f9d\u6b21\u9009\u4e2d\u5b57\u6bb5\u4ee5\u67e5\u770b\u53ca\u8c03\u6574\u6570\u503c
+dialog.pointedit.table.field=\u5b57\u6bb5
+dialog.pointedit.nofield=\u672a\u9009\u4e2d\u5b57\u6bb5
 dialog.pointedit.table.value=\u6570\u503c
 dialog.pointedit.table.value=\u6570\u503c
-dialog.pointedit.table.changed=\u5df2\u6539\u53d8
-dialog.pointedit.changevalue.text=\u8f93\u5165\u65b0\u6570\u503c
-dialog.pointedit.changevalue.title=\u7f16\u8f91\u6570\u636e\u6bb5
 dialog.pointnameedit.name=\u822a\u70b9\u540d\u79f0
 dialog.pointnameedit.uppercase=\u4e0a\u6863\u952e
 dialog.pointnameedit.lowercase=\u4e0b\u6863\u952e
 dialog.pointnameedit.name=\u822a\u70b9\u540d\u79f0
 dialog.pointnameedit.uppercase=\u4e0a\u6863\u952e
 dialog.pointnameedit.lowercase=\u4e0b\u6863\u952e
@@ -279,6 +322,27 @@ dialog.distances.toofewpoints=\u9700\u8981\u822a\u70b9\u6765\u8ba1\u7b97\u8ddd\u
 dialog.fullrangedetails.intro=\u822a\u6bb5\u8be6\u60c5
 dialog.fullrangedetails.coltotal=\u5305\u542b\u95f4\u65ad
 dialog.fullrangedetails.colsegments=\u4e0d\u5305\u542b\u95f4\u65ad
 dialog.fullrangedetails.intro=\u822a\u6bb5\u8be6\u60c5
 dialog.fullrangedetails.coltotal=\u5305\u542b\u95f4\u65ad
 dialog.fullrangedetails.colsegments=\u4e0d\u5305\u542b\u95f4\u65ad
+dialog.estimatetime.details=\u8be6\u60c5
+dialog.estimatetime.gentle=\u5e73\u7f13
+dialog.estimatetime.steep=\u9661\u5ced
+dialog.estimatetime.climb=\u4e0a\u5347
+dialog.estimatetime.descent=\u4e0b\u964d
+dialog.estimatetime.parameters=\u53c2\u6570
+dialog.estimatetime.parameters.timefor=\u9700\u65f6
+dialog.estimatetime.results=\u7ed3\u679c
+dialog.estimatetime.results.estimatedtime=\u4f30\u8ba1\u65f6\u95f4
+dialog.estimatetime.results.actualtime=\u5b9e\u9645\u7528\u65f6
+dialog.estimatetime.error.nodistance=\u4f30\u8ba1\u65f6\u95f4\u9700\u8981\u8fde\u7eed\u7684\u8f68\u8ff9
+dialog.estimatetime.error.noaltitudes=\u9009\u4e2d\u90e8\u5206\u4e0d\u5305\u542b\u9ad8\u5ea6\u4fe1\u606f
+dialog.learnestimationparams.intro=\u6b64\u8f68\u8ff9\u8ba1\u7b97\u5f97\u5230\u7684\u53c2\u6570\u4e3a
+dialog.learnestimationparams.averageerror=\u5e73\u5747\u8bef\u5dee
+dialog.learnestimationparams.combine=\u8fd9\u4e9b\u53c2\u6570\u53ef\u4e0e\u5f53\u524d\u6570\u503c\u5408\u5e76
+dialog.learnestimationparams.combinedresults=\u5408\u5e76\u7ed3\u679c
+dialog.learnestimationparams.weight.100pccurrent=\u4fdd\u6301\u5f53\u524d\u6570\u503c
+dialog.learnestimationparams.weight.current=\u5f53\u524d\u503c
+dialog.learnestimationparams.weight.calculated=\u8ba1\u7b97\u503c
+dialog.learnestimationparams.weight.50pc=\u5e73\u5747\u503c
+dialog.learnestimationparams.weight.100pccalculated=\u4f7f\u7528\u65b0\u8ba1\u7b97\u503c
 dialog.setmapbg.intro=\u8bf7\u9009\u62e9\u5730\u56fe\uff0c\u6216\u6dfb\u52a0\u5730\u56fe
 dialog.addmapsource.title=\u6dfb\u52a0\u5730\u56fe
 dialog.addmapsource.sourcename=\u5730\u56fe\u6765\u6e90\u540d\u79f0
 dialog.setmapbg.intro=\u8bf7\u9009\u62e9\u5730\u56fe\uff0c\u6216\u6dfb\u52a0\u5730\u56fe
 dialog.addmapsource.title=\u6dfb\u52a0\u5730\u56fe
 dialog.addmapsource.sourcename=\u5730\u56fe\u6765\u6e90\u540d\u79f0
@@ -364,7 +428,7 @@ dialog.deletemarked.nonefound=\u65e0\u6cd5\u5220\u9664\u6570\u636e\u70b9
 dialog.pastecoordinates.desc=\u5728\u6b64\u8f93\u5165\u6216\u7c98\u8d34\u5750\u6807\u70b9
 dialog.pastecoordinates.coords=\u5750\u6807\u70b9
 dialog.pastecoordinates.nothingfound=\u8bf7\u68c0\u67e5\u5750\u6807\u6570\u636e\u5e76\u91cd\u8bd5
 dialog.pastecoordinates.desc=\u5728\u6b64\u8f93\u5165\u6216\u7c98\u8d34\u5750\u6807\u70b9
 dialog.pastecoordinates.coords=\u5750\u6807\u70b9
 dialog.pastecoordinates.nothingfound=\u8bf7\u68c0\u67e5\u5750\u6807\u6570\u636e\u5e76\u91cd\u8bd5
-dialog.help.help=\u66f4\u591a\u4fe1\u606f\u548c\u7528\u6cd5\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\nhttp://activityworkshop.net/software/gpsprune/
+dialog.help.help=\u66f4\u591a\u4fe1\u606f\u548c\u7528\u6cd5\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\nhttp://gpsprune.activityworkshop.net///
 dialog.about.version=\u7248\u672c
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune\u662f\u4e00\u4e2a\u4eceGPS\u4e2d\u5bfc\u5165\u6570\u636e\uff0c\u663e\u793a\u6570\u636e\u548c\u7f16\u8f91\u6570\u636e\u7684\u8f6f\u4ef6
 dialog.about.version=\u7248\u672c
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune\u662f\u4e00\u4e2a\u4eceGPS\u4e2d\u5bfc\u5165\u6570\u636e\uff0c\u663e\u793a\u6570\u636e\u548c\u7f16\u8f91\u6570\u636e\u7684\u8f6f\u4ef6
@@ -403,7 +467,7 @@ dialog.checkversion.newversion1=\u53d1\u73b0\u65b0\u7248\u672c\uff01\u6700\u65b0
 dialog.checkversion.newversion2=
 dialog.checkversion.releasedate1=\u65b0\u7248\u672c\u53d1\u884c\u4e8e
 dialog.checkversion.releasedate2=
 dialog.checkversion.newversion2=
 dialog.checkversion.releasedate1=\u65b0\u7248\u672c\u53d1\u884c\u4e8e
 dialog.checkversion.releasedate2=
-dialog.checkversion.download=\u4e0b\u8f7d\u6700\u65b0\u7248\u672c\uff0c\u8bf7\u767b\u9646\u7f51\u7ad9\uff1a\nhttp:activityworkshop.net/software/gpsprune/download.html
+dialog.checkversion.download=\u4e0b\u8f7d\u6700\u65b0\u7248\u672c\uff0c\u8bf7\u767b\u9646\u7f51\u7ad9\uff1a\nhttp:gpsprune.activityworkshop.net/download.html
 dialog.keys.intro=\u53ef\u7528\u4e0b\u5217\u5feb\u6377\u952e\u66ff\u4ee3\u9f20\u6807
 dialog.keys.keylist=<table><tr><td>\u7bad\u5934</td><td>\u4e0a\u4e0b\u5de6\u53f3\u79fb\u52a8\u5730\u56fe</td></tr><tr><td>Ctrl + \u5de6\u53f3\u7bad\u5934</td><td>\u9009\u53d6\u524d\uff0c\u540e\u70b9</td></tr><tr><td>Ctrl + \u4e0a\u4e0b\u7bad\u5934</td><td>\u653e\u5927\u7f29\u5c0f</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\u9009\u62e9\u524d\u540e\u6bb5</td></tr><tr><td>Ctrl + Home, End</td><td>\u9009\u62e9\u9996\u672b\u70b9</td></tr><tr><td>Del</td><td>\u5220\u9664\u5f53\u524d\u70b9</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
 dialog.keys.intro=\u53ef\u7528\u4e0b\u5217\u5feb\u6377\u952e\u66ff\u4ee3\u9f20\u6807
 dialog.keys.keylist=<table><tr><td>\u7bad\u5934</td><td>\u4e0a\u4e0b\u5de6\u53f3\u79fb\u52a8\u5730\u56fe</td></tr><tr><td>Ctrl + \u5de6\u53f3\u7bad\u5934</td><td>\u9009\u53d6\u524d\uff0c\u540e\u70b9</td></tr><tr><td>Ctrl + \u4e0a\u4e0b\u7bad\u5934</td><td>\u653e\u5927\u7f29\u5c0f</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\u9009\u62e9\u524d\u540e\u6bb5</td></tr><tr><td>Ctrl + Home, End</td><td>\u9009\u62e9\u9996\u672b\u70b9</td></tr><tr><td>Del</td><td>\u5220\u9664\u5f53\u524d\u70b9</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -469,8 +533,8 @@ dialog.diskcache.maximumage=\u6700\u957f\u65f6\u95f4(\u5929)
 dialog.diskcache.deleteall=\u5220\u9664\u6240\u6709\u5730\u56fe\u5757
 dialog.diskcache.deleted1=\u5df2\u5220\u9664
 dialog.diskcache.deleted2=\u7f13\u5b58\u5185\u6587\u4ef6
 dialog.diskcache.deleteall=\u5220\u9664\u6240\u6709\u5730\u56fe\u5757
 dialog.diskcache.deleted1=\u5df2\u5220\u9664
 dialog.diskcache.deleted2=\u7f13\u5b58\u5185\u6587\u4ef6
-dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u6570\u636e
-dialog.deletefieldvalues.nofields=\u9009\u5b9a\u8303\u56f4\u5185\u6ca1\u6709\u8981\u5220\u9664\u7684\u6570\u636e
+dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u5b57\u6bb5
+dialog.deletefieldvalues.nofields=\u9009\u5b9a\u8303\u56f4\u5185\u6ca1\u6709\u8981\u5220\u9664\u7684\u5b57\u6bb5
 dialog.setlinewidth.text=\u8f93\u5165\u8f68\u8ff9\u7ebf\u5bbd\u50cf\u7d20\u503c(1-4)
 dialog.downloadosm.desc=\u786e\u8ba4\u4eceOSM\u4e0b\u8f7d\u8be5\u5730\u533a\u539f\u59cb\u6570\u636e:
 dialog.searchwikipedianames.search=\u67e5\u627e:
 dialog.setlinewidth.text=\u8f93\u5165\u8f68\u8ff9\u7ebf\u5bbd\u50cf\u7d20\u503c(1-4)
 dialog.downloadosm.desc=\u786e\u8ba4\u4eceOSM\u4e0b\u8f7d\u8be5\u5730\u533a\u539f\u59cb\u6570\u636e:
 dialog.searchwikipedianames.search=\u67e5\u627e:
@@ -478,9 +542,6 @@ dialog.searchwikipedianames.search=\u67e5\u627e:
 # 3d window
 dialog.3d.title=GpsPrune 3D \u663e\u793a
 dialog.3d.altitudefactor=\u9ad8\u5ea6\u653e\u5927\u7cfb\u6570
 # 3d window
 dialog.3d.title=GpsPrune 3D \u663e\u793a
 dialog.3d.altitudefactor=\u9ad8\u5ea6\u653e\u5927\u7cfb\u6570
-dialog.3dlines.title=GpsPrune \u7f51\u683c\u7ebf
-dialog.3dlines.empty=\u65e0\u6cd5\u663e\u793a\u7f51\u683c\u7ebf
-dialog.3dlines.intro=3D \u7f51\u683c\u7ebf
 
 # Confirm messages
 confirm.loadfile=\u6570\u636e\u5df2\u4ece\u6587\u4ef6\u5bfc\u5165
 
 # Confirm messages
 confirm.loadfile=\u6570\u636e\u5df2\u4ece\u6587\u4ef6\u5bfc\u5165
@@ -529,7 +590,6 @@ button.cancel=\u53d6\u6d88
 button.overwrite=\u8986\u76d6
 button.moveup=\u4e0a\u79fb
 button.movedown=\u4e0b\u79fb
 button.overwrite=\u8986\u76d6
 button.moveup=\u4e0a\u79fb
 button.movedown=\u4e0b\u79fb
-button.showlines=\u663e\u793a\u7ebf\u6761
 button.edit=\u7f16\u8f91
 button.exit=\u9000\u51fa
 button.close=\u5173\u95ed
 button.edit=\u7f16\u8f91
 button.exit=\u9000\u51fa
 button.close=\u5173\u95ed
@@ -552,6 +612,7 @@ button.browse=\u6d4f\u89c8...
 button.addnew=\u6dfb\u52a0
 button.delete=\u5220\u9664
 button.manage=\u7ba1\u7406
 button.addnew=\u6dfb\u52a0
 button.delete=\u5220\u9664
 button.manage=\u7ba1\u7406
+button.combine=\u5408\u5e76
 
 # File types
 filetype.txt=TXT\u6587\u4ef6
 
 # File types
 filetype.txt=TXT\u6587\u4ef6
@@ -562,12 +623,14 @@ filetype.kmz=KMZ\u6587\u4ef6
 filetype.gpx=GPX\u6587\u4ef6
 filetype.pov=POV\u6587\u4ef6
 filetype.svg=SVG\u6587\u4ef6
 filetype.gpx=GPX\u6587\u4ef6
 filetype.pov=POV\u6587\u4ef6
 filetype.svg=SVG\u6587\u4ef6
+filetype.png=PNG\u6587\u4ef6
 filetype.audio=WAV,OGG,MP3\u6587\u4ef6
 
 # Display components
 display.nodata=\u65e0\u6570\u636e
 display.noaltitudes=\u8f68\u8ff9\u6570\u636e\u4e0d\u542b\u9ad8\u5ea6\u4fe1\u606f
 display.notimestamps=\u8f68\u8ff9\u6570\u636e\u672a\u542b\u65f6\u95f4\u4fe1\u606f
 filetype.audio=WAV,OGG,MP3\u6587\u4ef6
 
 # Display components
 display.nodata=\u65e0\u6570\u636e
 display.noaltitudes=\u8f68\u8ff9\u6570\u636e\u4e0d\u542b\u9ad8\u5ea6\u4fe1\u606f
 display.notimestamps=\u8f68\u8ff9\u6570\u636e\u672a\u542b\u65f6\u95f4\u4fe1\u606f
+display.novalues=\u8f68\u8ff9\u6570\u636e\u4e0d\u4fdd\u542b\u6b64\u5b57\u6bb5
 details.trackdetails=\u8f68\u8ff9\u4fe1\u606f
 details.notrack=\u65e0\u8f68\u8ff9
 details.track.points=\u8f68\u8ff9\u70b9
 details.trackdetails=\u8f68\u8ff9\u4fe1\u606f
 details.notrack=\u65e0\u8f68\u8ff9
 details.track.points=\u8f68\u8ff9\u70b9
@@ -638,21 +701,31 @@ units.feet=\u82f1\u5c3a
 units.feet.short=\u82f1\u5c3a
 units.kilometres=\u5343\u7c73
 units.kilometres.short=\u5343\u7c73
 units.feet.short=\u82f1\u5c3a
 units.kilometres=\u5343\u7c73
 units.kilometres.short=\u5343\u7c73
+units.kilometresperhour=\u5343\u7c73/\u65f6
 units.kilometresperhour.short=\u5343\u7c73/\u65f6
 units.miles=\u82f1\u91cc
 units.miles.short=\u82f1\u91cc
 units.kilometresperhour.short=\u5343\u7c73/\u65f6
 units.miles=\u82f1\u91cc
 units.miles.short=\u82f1\u91cc
+units.milesperhour=\u82f1\u91cc/\u65f6
 units.milesperhour.short=\u82f1\u91cc/\u65f6
 units.nauticalmiles=\u6d77\u91cc
 units.nauticalmiles.short=\u6d77\u91cc
 units.nauticalmilesperhour.short=\u6d77\u91cc/\u65f6
 units.milesperhour.short=\u82f1\u91cc/\u65f6
 units.nauticalmiles=\u6d77\u91cc
 units.nauticalmiles.short=\u6d77\u91cc
 units.nauticalmilesperhour.short=\u6d77\u91cc/\u65f6
+units.metrespersec=\u7c73/\u79d2
 units.metrespersec.short=\u7c73/\u79d2
 units.metrespersec.short=\u7c73/\u79d2
+units.feetpersec=\u82f1\u5c3a/\u79d2
 units.feetpersec.short=\u82f1\u5c3a/\u79d2
 units.hours=\u5c0f\u65f6
 units.feetpersec.short=\u82f1\u5c3a/\u79d2
 units.hours=\u5c0f\u65f6
+units.minutes=\u5206
+units.seconds=\u79d2
 units.degminsec=\u5ea6-\u5206-\u79d2
 units.degmin=\u5ea6-\u5206
 units.deg=\u5ea6
 units.iso8601=ISO 8601
 
 units.degminsec=\u5ea6-\u5206-\u79d2
 units.degmin=\u5ea6-\u5206
 units.deg=\u5ea6
 units.iso8601=ISO 8601
 
+# How to combine conditions, such as filters
+logic.and=\u4e0e
+logic.or=\u6216
+
 # External urls
 url.googlemaps=ditu.google.cn
 wikipedia.lang=zh
 # External urls
 url.googlemaps=ditu.google.cn
 wikipedia.lang=zh
@@ -721,7 +794,7 @@ error.undofailed.text=\u64a4\u9500\u64cd\u4f5c\u5931\u8d25
 error.function.noop.title=\u529f\u80fd\u65e0\u6548
 error.rearrange.noop=\u91cd\u65b0\u914d\u7f6e\u822a\u70b9\u65e0\u6548
 error.function.notavailable.title=\u65e0\u6b64\u529f\u80fd
 error.function.noop.title=\u529f\u80fd\u65e0\u6548
 error.rearrange.noop=\u91cd\u65b0\u914d\u7f6e\u822a\u70b9\u65e0\u6548
 error.function.notavailable.title=\u65e0\u6b64\u529f\u80fd
-error.function.nojava3d=\u6b64\u529f\u80fd\u9700\u8981 Java 3D\uff0c\u53ef\u4eceSun.com\u83b7\u5f97
+error.function.nojava3d=\u6b64\u529f\u80fd\u9700\u8981 Java 3D
 error.3d=3D \u663e\u793a\u9519\u8bef
 error.readme.notfound=\u627e\u4e0d\u5230\u7248\u672c\u4fe1\u606f\u6587\u4ef6
 error.osmimage.dialogtitle=\u5bfc\u5165\u5730\u56fe\u65f6\u9519\u8bef
 error.3d=3D \u663e\u793a\u9519\u8bef
 error.readme.notfound=\u627e\u4e0d\u5230\u7248\u672c\u4fe1\u606f\u6587\u4ef6
 error.osmimage.dialogtitle=\u5bfc\u5165\u5730\u56fe\u65f6\u9519\u8bef
@@ -738,3 +811,4 @@ error.cache.notthere=\u672a\u627e\u5230\u533a\u57df\u6570\u636e\u7f13\u5b58\u658
 error.cache.empty=\u533a\u57df\u6570\u636e\u6587\u4ef6\u5939\u7a7a
 error.cache.cannotdelete=\u65e0\u53ef\u5220\u9664\u533a\u57df\u6570\u636e
 error.interpolate.invalidparameter=\u8f93\u5165\u70b9\u6570\u91cf\u5fc5\u987b\u57281\u52301000\u4e4b\u95f4
 error.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
index ed7e627f424ea2794d51641d1ef791cca4b1481a..2d1c9e80098ee85d29250b68b36b5c075389a3bc 100644 (file)
@@ -3,7 +3,7 @@ package tim.prune.load;
 /**
  * Class to manage the list of file formats supported by Gpsbabel
  * (older versions of gpsbabel might not support all of these, of course).
 /**
  * Class to manage the list of file formats supported by Gpsbabel
  * (older versions of gpsbabel might not support all of these, of course).
- * Certain supported formats such as txt, csv, gpx are not included here
+ * Certain supported formats such as txt, csv are not included here
  * as GpsPrune can already load them directly.
  */
 public abstract class BabelFileFormats
  * as GpsPrune can already load them directly.
  */
 public abstract class BabelFileFormats
@@ -111,6 +111,7 @@ public abstract class BabelFileFormats
                        "GPSman", "gpsman", null,
                        "GPSPilot Tracker for Palm/OS", "gpspilot", null,
                        "gpsutil", "gpsutil", null,
                        "GPSman", "gpsman", null,
                        "GPSPilot Tracker for Palm/OS", "gpspilot", null,
                        "gpsutil", "gpsutil", null,
+                       "GPX", "gpx", ".gpx",
                        "HikeTech", "hiketech", null,
                        "Holux (gm-100) .wpo Format", "holux", null,
                        "Holux M-241 (MTK based) Binary File Format", "m241-bin", null,
                        "HikeTech", "hiketech", null,
                        "Holux (gm-100) .wpo Format", "holux", null,
                        "Holux M-241 (MTK based) Binary File Format", "m241-bin", null,
index f7866efaf0cacc1512dbca0facc3b2aa509df944..1897e0cdcf9d717e442ffe2522c43223a2d466fb 100644 (file)
@@ -25,6 +25,7 @@ import tim.prune.config.Config;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
 import tim.prune.gui.GuiGridLayout;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
 import tim.prune.gui.GuiGridLayout;
+import tim.prune.load.babel.BabelFilterPanel;
 
 
 /**
 
 
 /**
@@ -158,6 +159,15 @@ public class BabelLoadFromFile extends BabelLoader
                _saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_saveCheckbox);
 
                _saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_saveCheckbox);
 
+               // Filter panel
+               _filterPanel = new BabelFilterPanel(_parentFrame);
+               // Give filter panel the contents of the config
+               String filter = Config.getConfigString(Config.KEY_GPSBABEL_FILTER);
+               if (filter != null) {
+                       _filterPanel.setFilterString(filter);
+               }
+               mainPanel.add(_filterPanel);
+
                // progress bar (initially invisible)
                _progressBar = new JProgressBar(0, 10);
                mainPanel.add(_progressBar);
                // progress bar (initially invisible)
                _progressBar = new JProgressBar(0, 10);
                mainPanel.add(_progressBar);
@@ -216,6 +226,10 @@ public class BabelLoadFromFile extends BabelLoader
         */
        protected void saveConfigValues()
        {
         */
        protected void saveConfigValues()
        {
-               // nothing needed
+               // Save the filter string (but don't remove it if it's now blank)
+               final String filter = _filterPanel.getFilterString();
+               if (filter != null && !filter.equals("")) {
+                       Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
+               }
        }
 }
        }
 }
index 8c4b4dd17c2170121c8e9f2cf7e28d3cb2b2e1ce..130ca793dd30bc118fb637fa485971619ab73241 100644 (file)
@@ -26,6 +26,7 @@ import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
 import tim.prune.config.Config;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo.FILE_TYPE;
+import tim.prune.load.babel.BabelFilterPanel;
 
 /**
  * Class to manage the loading of data from a GPS device using GpsBabel
 
 /**
  * Class to manage the loading of data from a GPS device using GpsBabel
@@ -128,6 +129,15 @@ public class BabelLoadFromGps extends BabelLoader
                _saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_saveCheckbox);
 
                _saveCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_saveCheckbox);
 
+               // Filter panel
+               _filterPanel = new BabelFilterPanel(_parentFrame);
+               // Give filter panel the contents of the config
+               String filter = Config.getConfigString(Config.KEY_GPSBABEL_FILTER);
+               if (filter != null) {
+                       _filterPanel.setFilterString(filter);
+               }
+               mainPanel.add(_filterPanel);
+
                // progress bar (initially invisible)
                _progressBar = new JProgressBar(0, 10);
                mainPanel.add(_progressBar);
                // progress bar (initially invisible)
                _progressBar = new JProgressBar(0, 10);
                mainPanel.add(_progressBar);
@@ -169,7 +179,9 @@ public class BabelLoadFromGps extends BabelLoader
        {
                final String device = _deviceField.getText().trim();
                final String format = _formatField.getText().trim();
        {
                final String device = _deviceField.getText().trim();
                final String format = _formatField.getText().trim();
+               final String filter = _filterPanel.getFilterString();
                Config.setConfigString(Config.KEY_GPS_DEVICE, device);
                Config.setConfigString(Config.KEY_GPS_FORMAT, format);
                Config.setConfigString(Config.KEY_GPS_DEVICE, device);
                Config.setConfigString(Config.KEY_GPS_FORMAT, format);
+               Config.setConfigString(Config.KEY_GPSBABEL_FILTER, filter);
        }
 }
        }
 }
index b970665c0c36d3f19de4d3d3583d4da9b1ce1c2a..7ebd7deadc73a50023cb5baecc76936296a3eae9 100644 (file)
@@ -20,8 +20,8 @@ import tim.prune.ExternalTools;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo;
+import tim.prune.load.babel.BabelFilterPanel;
 import tim.prune.load.xml.XmlFileLoader;
 import tim.prune.load.xml.XmlHandler;
 import tim.prune.save.GpxExporter;
 import tim.prune.load.xml.XmlFileLoader;
 import tim.prune.load.xml.XmlHandler;
 import tim.prune.save.GpxExporter;
@@ -42,6 +42,7 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
        protected JProgressBar _progressBar = null;
        protected File _saveFile = null;
        protected boolean _cancelled = false;
        protected JProgressBar _progressBar = null;
        protected File _saveFile = null;
        protected boolean _cancelled = false;
+       protected BabelFilterPanel _filterPanel = null;
 
 
        /**
 
 
        /**
@@ -91,7 +92,11 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
 
 
        /** Do any subclass-specific dialog initialisation necessary */
 
 
        /** Do any subclass-specific dialog initialisation necessary */
-       protected void initDialog() {}
+       protected void initDialog()
+       {
+               // GPSBabel filter, if any
+               _filterPanel.setFilterString(Config.getConfigString(Config.KEY_GPSBABEL_FILTER));
+       }
 
        /**
         * @param inStart true if the dialog is restarting
 
        /**
         * @param inStart true if the dialog is restarting
@@ -229,9 +234,8 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
                        if (errorMessage.length() > 0) {throw new Exception(errorMessage);}
 
                        // Send data back to app
                        if (errorMessage.length() > 0) {throw new Exception(errorMessage);}
 
                        // Send data back to app
-                       _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(), Altitude.Format.METRES,
-                               getSourceInfo(),
-                               handler.getTrackNameList());
+                       _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(), null,
+                               getSourceInfo(), handler.getTrackNameList());
                }
        }
 
                }
        }
 
@@ -242,33 +246,55 @@ public abstract class BabelLoader extends GenericFunction implements Runnable
         */
        private String[] getCommandArray()
        {
         */
        private String[] getCommandArray()
        {
-               String[] commands = null;
+               ArrayList<String> commandList = new ArrayList<String>();
+               // Firstly the command for gpsbabel itself
                final String command = Config.getConfigString(Config.KEY_GPSBABEL_PATH);
                final String command = Config.getConfigString(Config.KEY_GPSBABEL_PATH);
+               commandList.add(command);
+               // Then whether to load waypoints or track points
                final boolean loadWaypoints = _waypointCheckbox.isSelected();
                final boolean loadTrack = _trackCheckbox.isSelected();
                final boolean loadWaypoints = _waypointCheckbox.isSelected();
                final boolean loadTrack = _trackCheckbox.isSelected();
-               if (loadWaypoints && loadTrack) {
-                       // Both waypoints and track points selected
-                       commands = new String[] {command, "-w", "-t", "-i", getInputFormat(),
-                               "-f", getFilePath(), "-o", "gpx", "-F", "-"};
+               if (loadWaypoints) {
+                       commandList.add("-w");
                }
                }
-               else
+               if (loadTrack) {
+                       commandList.add("-t");
+               }
+               // Input format
+               commandList.add("-i");
+               commandList.add(getInputFormat());
+               // File path
+               commandList.add("-f");
+               commandList.add(getFilePath());
+               // Filters, if any
+               final String filter = _filterPanel.getFilterString();
+               if (filter != null && !filter.equals(""))
                {
                {
-                       // Only waypoints OR track points selected
-                       commands = new String[] {command, "-w", "-i", getInputFormat(),
-                               "-f", getFilePath(), "-o", "gpx", "-F", "-"};
-                       if (loadTrack) {
-                               commands[1] = "-t";
+                       for (String arg : filter.split(" "))
+                       {
+                               if (arg.length() > 0) {
+                                       commandList.add(arg);
+                               }
                        }
                }
                        }
                }
+               // Output format
+               commandList.add("-o");
+               commandList.add("gpx");
+               // Where to
+               commandList.add("-F");
+               String whereTo = "-";
                // Do we want to save the gpx straight to file?
                // Do we want to save the gpx straight to file?
-               if (_saveCheckbox.isSelected()) {
+               if (_saveCheckbox.isSelected())
+               {
                        // Select file to save to
                        _saveFile = GpxExporter.chooseGpxFile(_parentFrame);
                        if (_saveFile != null) {
                        // Select file to save to
                        _saveFile = GpxExporter.chooseGpxFile(_parentFrame);
                        if (_saveFile != null) {
-                               commands[commands.length-1] = _saveFile.getAbsolutePath();
+                               whereTo = _saveFile.getAbsolutePath();
                        }
                }
                        }
                }
-               return commands;
+               commandList.add(whereTo);
+               // Convert to string array
+               String[] args = new String[] {};
+               return commandList.toArray(args);
        }
 
        /**
        }
 
        /**
diff --git a/tim/prune/load/ComponentHider.java b/tim/prune/load/ComponentHider.java
new file mode 100644 (file)
index 0000000..45eeccd
--- /dev/null
@@ -0,0 +1,59 @@
+package tim.prune.load;
+
+import java.awt.Component;
+import java.util.ArrayList;
+
+import tim.prune.data.Field;
+
+/**
+ * Class to hold a list of Components and fields,
+ * and then enable or disable them (setEnabled) according
+ * to whether those fields are available or not
+ */
+public class ComponentHider
+{
+       /**
+        * Inner class to hold each Component and its Field
+        */
+       static class ComponentPair
+       {
+               public Component _component = null;
+               public Field     _field     = null;
+               /** Constructor */
+               public ComponentPair(Component inComponent, Field inField)
+               {
+                       _component = inComponent;
+                       _field     = inField;
+               }
+       }
+
+       /** list itself */
+       private ArrayList<ComponentPair> _componentList = new ArrayList<ComponentPair>(20);
+
+       /**
+        * Add a new component to be controlled
+        * @param inComponent component to enable/disable
+        * @param inField associated field
+        */
+       public void addComponent(Component inComponent, Field inField)
+       {
+               if (inComponent != null && inField != null) {
+                       _componentList.add(new ComponentPair(inComponent, inField));
+               }
+       }
+
+       /**
+        * Enable or disable the components for the given field
+        * @param inField field
+        * @param inEnabled true for enabled, false for disabled
+        */
+       public void enableComponents(Field inField, boolean inEnabled)
+       {
+               for (ComponentPair pair : _componentList)
+               {
+                       if (pair != null && pair._field == inField) {
+                               pair._component.setEnabled(inEnabled);
+                       }
+               }
+       }
+}
index 9b2bdc787a9c9a0833d7d5efd08380535b5dcc21..c76318d16bfb0be060e315a52a2a35a9eeade06e 100644 (file)
@@ -19,20 +19,42 @@ public abstract class FieldGuesser
         */
        private static boolean isHeaderRow(String[] inValues)
        {
         */
        private static boolean isHeaderRow(String[] inValues)
        {
-               // Loop over values looking for a Latitude value
+               // Loop over values seeing if any are mostly numeric
                if (inValues != null)
                {
                if (inValues != null)
                {
-                       for (int v=0; v<inValues.length; v++)
+                       for (String value : inValues)
                        {
                        {
-                               Latitude lat = new Latitude(inValues[v]);
-                               if (lat.isValid()) {return false;}
+                               if (fieldLooksNumeric(value)) {return false;}
                        }
                }
                        }
                }
-               // No valid Latitude value found so presume header
+               // No (mostly) numeric values found so presume header
                return true;
        }
 
 
                return true;
        }
 
 
+       /**
+        * See if a field looks numeric or not by comparing the number of numeric vs non-numeric characters
+        * @param inValue field value to check
+        * @return true if there are more numeric characters than not
+        */
+       private static boolean fieldLooksNumeric(String inValue)
+       {
+               if (inValue == null) {
+                       return false;
+               }
+               final int numChars = inValue.length();
+               if (numChars < 3) {return false;} // Don't care about one or two character values
+               // Loop through characters seeing which ones are numeric and which not
+               int numNums = 0;
+               for (int i=0; i<numChars; i++)
+               {
+                       char currChar = inValue.charAt(i);
+                       if (currChar >= '0' && currChar <= '9') {numNums++;}
+               }
+               // Return true if more than half the characters are numeric
+               return numNums > (numChars/2);
+       }
+
        /**
         * Try to guess the fields for the given values from the file
         * @param inValues array of values from first non-blank line of file
        /**
         * Try to guess the fields for the given values from the file
         * @param inValues array of values from first non-blank line of file
@@ -108,12 +130,24 @@ public abstract class FieldGuesser
                                else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
                                        fields[f] = Field.LONGITUDE;
                                }
                                else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
                                        fields[f] = Field.LONGITUDE;
                                }
-                               else {
-                                       customFieldNum++;
-                                       fields[f] = new Field(customPrefix + (customFieldNum));
+                               else
+                               {
+                                       // Can we use the field name given?
+                                       Field customField = null;
+                                       if (isHeader && inValues[f] != null && inValues[f].length() > 0) {
+                                               customField = new Field(inValues[f]);
+                                       }
+                                       // Find an unused field number
+                                       while (customField == null || checkArrayHasField(fields, customField))
+                                       {
+                                               customFieldNum++;
+                                               customField = new Field(customPrefix + (customFieldNum));
+                                       }
+                                       fields[f] = customField;
                                }
                        }
                }
                                }
                        }
                }
+
                // Do a final check to make sure lat and long are in there
                if (!checkArrayHasField(fields, Field.LATITUDE)) {
                        fields[0] = Field.LATITUDE;
                // Do a final check to make sure lat and long are in there
                if (!checkArrayHasField(fields, Field.LATITUDE)) {
                        fields[0] = Field.LATITUDE;
index 1d12397dd61514a904faae91b0d32902c341afe2..da354aac8b253895324e84b8a2b67d0ed74bbcb3 100644 (file)
@@ -42,8 +42,14 @@ public class FileCacher
                        {
                                reader = new BufferedReader(new FileReader(_file));
                                String currLine = reader.readLine();
                        {
                                reader = new BufferedReader(new FileReader(_file));
                                String currLine = reader.readLine();
+                               if (currLine != null && currLine.startsWith("<?xml")) {
+                                       return; // it's an xml file, it shouldn't use this cacher
+                               }
                                while (currLine != null)
                                {
                                while (currLine != null)
                                {
+                                       if (currLine.indexOf('\0') >= 0) {
+                                               return; // it's a binary file, shouldn't use this cacher
+                                       }
                                        if (currLine.trim().length() > 0)
                                                contentList.add(currLine);
                                        currLine = reader.readLine();
                                        if (currLine.trim().length() > 0)
                                                contentList.add(currLine);
                                        currLine = reader.readLine();
index 5672c69fa9cc6052e4b303efb2ad28a252068cf0..8669e189c94043b4e3ab91889c4acf4b5e52dc0e 100644 (file)
@@ -2,11 +2,14 @@ package tim.prune.load;
 
 import java.io.File;
 import java.util.ArrayList;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.TreeSet;
+
 import javax.swing.JFileChooser;
 import javax.swing.JFrame;
 
 import tim.prune.App;
 import tim.prune.config.Config;
 import javax.swing.JFileChooser;
 import javax.swing.JFrame;
 
 import tim.prune.App;
 import tim.prune.config.Config;
+import tim.prune.data.Photo;
 import tim.prune.load.xml.GzipFileLoader;
 import tim.prune.load.xml.XmlFileLoader;
 import tim.prune.load.xml.ZipFileLoader;
 import tim.prune.load.xml.GzipFileLoader;
 import tim.prune.load.xml.XmlFileLoader;
 import tim.prune.load.xml.ZipFileLoader;
@@ -130,6 +133,14 @@ public class FileLoader
                {
                        _nmeaFileLoader.openFile(inFile);
                }
                {
                        _nmeaFileLoader.openFile(inFile);
                }
+               else if (fileExtension.equals(".jpg") || fileExtension.equals("jpeg"))
+               {
+                       Photo photo = JpegLoader.createPhoto(inFile);
+                       TreeSet<Photo> photoSet = new TreeSet<Photo>();
+                       photoSet.add(photo);
+                       _app.informPhotosLoaded(photoSet);
+                       _app.informNoDataLoaded(); // To trigger load of next file if any
+               }
                else
                {
                        // Use text loader for everything else
                else
                {
                        // Use text loader for everything else
index 8a376d8e6367b81aff9a9fca9f2c1f4edd1bafaa..a177eaad9f77455efa2f4ba51ac66d3eb0ce3660 100644 (file)
@@ -20,6 +20,7 @@ import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
 import tim.prune.data.Photo;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Longitude;
 import tim.prune.data.Photo;
 import tim.prune.data.Timestamp;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.function.Cancellable;
 import tim.prune.jpeg.ExifGateway;
 import tim.prune.jpeg.JpegData;
 import tim.prune.function.Cancellable;
 import tim.prune.jpeg.ExifGateway;
 import tim.prune.jpeg.JpegData;
@@ -318,7 +319,7 @@ public class JpegLoader implements Runnable, Cancellable
                Longitude longitude = new Longitude(lonval, Longitude.FORMAT_DEG_MIN_SEC);
                Altitude altitude = null;
                if (inData.hasAltitude()) {
                Longitude longitude = new Longitude(lonval, Longitude.FORMAT_DEG_MIN_SEC);
                Altitude altitude = null;
                if (inData.hasAltitude()) {
-                       altitude = new Altitude(inData.getAltitude(), Altitude.Format.METRES);
+                       altitude = new Altitude(inData.getAltitude(), UnitSetLibrary.UNITS_METRES);
                }
                return new DataPoint(latitude, longitude, altitude);
        }
                }
                return new DataPoint(latitude, longitude, altitude);
        }
index 4ed2ab0f213d64f89be89520e3f1b89f923a0840..a052b55d14fca6dabc18497775575005945410bd 100644 (file)
@@ -94,7 +94,6 @@ public class MediaLoadProgressDialog
                        _progressBar.setMaximum(inMax);
                _progressBar.setValue(inCurrent);
                _progressBar.setString("" + inCurrent + " / " + _progressBar.getMaximum());
                        _progressBar.setMaximum(inMax);
                _progressBar.setValue(inCurrent);
                _progressBar.setString("" + inCurrent + " / " + _progressBar.getMaximum());
-               // TODO: Need to repaint?
        }
 
        /**
        }
 
        /**
index 2dd63dec16088fa4350b5b914c2c25a8d59ccdbc..d62ba5fe1660a08a3398004b5b9e44b710f3d1c4 100644 (file)
@@ -7,7 +7,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 
 import tim.prune.App;
 import java.util.ArrayList;
 
 import tim.prune.App;
-import tim.prune.data.Altitude;
 import tim.prune.data.Field;
 import tim.prune.data.SourceInfo;
 
 import tim.prune.data.Field;
 import tim.prune.data.SourceInfo;
 
@@ -92,8 +91,7 @@ public class NmeaFileLoader
                if (messages.size() > 0)
                {
                        _app.informDataLoaded(getFieldArray(), makeDataArray(messages),
                if (messages.size() > 0)
                {
                        _app.informDataLoaded(getFieldArray(), makeDataArray(messages),
-                               Altitude.Format.METRES, new SourceInfo(inFile, SourceInfo.FILE_TYPE.NMEA),
-                               null);
+                               null, new SourceInfo(inFile, SourceInfo.FILE_TYPE.NMEA), null);
                }
        }
 
                }
        }
 
index 1f88012807485d8010f387947bb00d9621375a03..53d6d2a77d50de6355be51ff0f19af1808b9d808 100644 (file)
@@ -1,7 +1,6 @@
 package tim.prune.load;
 
 import java.awt.BorderLayout;
 package tim.prune.load;
 
 import java.awt.BorderLayout;
-import java.awt.CardLayout;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
@@ -22,9 +21,13 @@ import java.io.File;
 
 import tim.prune.App;
 import tim.prune.I18nManager;
 
 import tim.prune.App;
 import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
 import tim.prune.data.Field;
 import tim.prune.data.Field;
+import tim.prune.data.PointCreateOptions;
 import tim.prune.data.SourceInfo;
 import tim.prune.data.SourceInfo;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WizardLayout;
 
 
 /**
 
 
 /**
@@ -37,8 +40,7 @@ public class TextFileLoader
        private App _app = null;
        private JFrame _parentFrame = null;
        private JDialog _dialog = null;
        private App _app = null;
        private JFrame _parentFrame = null;
        private JDialog _dialog = null;
-       private JPanel _cardPanel = null;
-       private CardLayout _layout = null;
+       private WizardLayout _wizard = null;
        private JButton _backButton = null, _nextButton = null;
        private JButton _finishButton = null;
        private JButton _moveUpButton = null, _moveDownButton = null;
        private JButton _backButton = null, _nextButton = null;
        private JButton _finishButton = null;
        private JButton _moveUpButton = null, _moveDownButton = null;
@@ -51,14 +53,18 @@ public class TextFileLoader
        private FileExtractTableModel _fileExtractTableModel = null;
        private JTable _fieldTable;
        private FieldSelectionTableModel _fieldTableModel = null;
        private FileExtractTableModel _fileExtractTableModel = null;
        private JTable _fieldTable;
        private FieldSelectionTableModel _fieldTableModel = null;
-       private JComboBox _unitsDropDown = null;
+       private JComboBox _altitudeUnitsDropdown = null;
+       private JComboBox _hSpeedUnitsDropdown = null;
+       private JComboBox _vSpeedUnitsDropdown = null;
+       private JRadioButton _vSpeedUpwardsRadio = null;
+       private ComponentHider _componentHider = null;
        private int _selectedField = -1;
        private char _currentDelimiter = ',';
 
        // previously selected values
        private char _lastUsedDelimiter = ',';
        private Field[] _lastSelectedFields = null;
        private int _selectedField = -1;
        private char _currentDelimiter = ',';
 
        // previously selected values
        private char _lastUsedDelimiter = ',';
        private Field[] _lastSelectedFields = null;
-       private Altitude.Format _lastAltitudeFormat = Altitude.Format.NO_FORMAT;
+       private Unit _lastAltitudeUnit = null;
 
        // constants
        private static final int SNIPPET_SIZE = 6;
 
        // constants
        private static final int SNIPPET_SIZE = 6;
@@ -135,9 +141,11 @@ public class TextFileLoader
                        _dialog.pack();
                        _dialog.setVisible(true);
                }
                        _dialog.pack();
                        _dialog.setVisible(true);
                }
-               else {
+               else
+               {
                        // Didn't pass pre-check
                        // Didn't pass pre-check
-                       _app.showErrorMessage("error.load.dialogtitle", "error.load.noread");
+                       _app.showErrorMessageNoLookup("error.load.dialogtitle",
+                               I18nManager.getText("error.load.noread") + ": " + inFile.getName());
                        _app.informNoDataLoaded();
                }
        }
                        _app.informNoDataLoaded();
                }
        }
@@ -160,6 +168,9 @@ public class TextFileLoader
 
                // Check each line of the file
                String[] fileContents = _fileCacher.getContents();
 
                // Check each line of the file
                String[] fileContents = _fileCacher.getContents();
+               if (fileContents == null) {
+                       return false; // nothing cached, might be binary
+               }
                boolean fileOK = true;
                _delimiterInfos = new DelimiterInfo[5];
                for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
                boolean fileOK = true;
                _delimiterInfos = new DelimiterInfo[5];
                for (int i=0; i<4; i++) _delimiterInfos[i] = new DelimiterInfo(DELIMITERS[i]);
@@ -233,9 +244,9 @@ public class TextFileLoader
                _backButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                _backButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               _layout.previous(_cardPanel);
-                               _backButton.setEnabled(false);
-                               _nextButton.setEnabled(true);
+                               _wizard.showPreviousCard();
+                               _nextButton.setEnabled(!_wizard.isLastCard());
+                               _backButton.setEnabled(!_wizard.isFirstCard());
                                _finishButton.setEnabled(false);
                        }
                });
                                _finishButton.setEnabled(false);
                        }
                });
@@ -245,11 +256,11 @@ public class TextFileLoader
                _nextButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                _nextButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               prepareSecondPanel();
-                               _layout.next(_cardPanel);
-                               _nextButton.setEnabled(false);
-                               _backButton.setEnabled(true);
-                               _finishButton.setEnabled(_fieldTableModel.getRowCount() > 1);
+                               prepareNextPanel(); // Maybe it needs to be initialized based on previous panels
+                               _wizard.showNextCard();
+                               _nextButton.setEnabled(!_wizard.isLastCard() && isCurrentCardValid());
+                               _backButton.setEnabled(!_wizard.isFirstCard());
+                               _finishButton.setEnabled(_wizard.isLastCard() && isCurrentCardValid());
                        }
                });
                buttonPanel.add(_nextButton);
                        }
                });
                buttonPanel.add(_nextButton);
@@ -273,10 +284,9 @@ public class TextFileLoader
                buttonPanel.add(cancelButton);
                wholePanel.add(buttonPanel, BorderLayout.SOUTH);
 
                buttonPanel.add(cancelButton);
                wholePanel.add(buttonPanel, BorderLayout.SOUTH);
 
-               // Make the two cards, for delimiter and fields
-               _cardPanel = new JPanel();
-               _layout = new CardLayout();
-               _cardPanel.setLayout(_layout);
+               // Make the card panel in the centre
+               JPanel cardPanel = new JPanel();
+               _wizard = new WizardLayout(cardPanel);
                JPanel firstCard = new JPanel();
                firstCard.setLayout(new BorderLayout());
                firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
                JPanel firstCard = new JPanel();
                firstCard.setLayout(new BorderLayout());
                firstCard.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
@@ -391,17 +401,71 @@ public class TextFileLoader
 
                innerPanel2.add(innerPanel3, BorderLayout.EAST);
                secondCard.add(innerPanel2, BorderLayout.CENTER);
 
                innerPanel2.add(innerPanel3, BorderLayout.EAST);
                secondCard.add(innerPanel2, BorderLayout.CENTER);
+
+               // Third card, for units selection of altitude and speeds
+               JPanel thirdCard = new JPanel();
+               thirdCard.setLayout(new BorderLayout(10, 10));
+               JPanel holderPanel = new JPanel();
+               holderPanel.setLayout(new BoxLayout(holderPanel, BoxLayout.Y_AXIS));
+               // Altitude
                JPanel altUnitsPanel = new JPanel();
                JPanel altUnitsPanel = new JPanel();
-               altUnitsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
-               altUnitsPanel.add(new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits")));
-               String[] units = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
-               _unitsDropDown = new JComboBox(units);
-               altUnitsPanel.add(_unitsDropDown);
-               secondCard.add(altUnitsPanel, BorderLayout.SOUTH);
-               _cardPanel.add(firstCard, "card1");
-               _cardPanel.add(secondCard, "card2");
-
-               wholePanel.add(_cardPanel, BorderLayout.CENTER);
+               GuiGridLayout altGrid = new GuiGridLayout(altUnitsPanel);
+               altUnitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.altitude")));
+               JLabel altLabel = new JLabel(I18nManager.getText("dialog.openoptions.altitudeunits") + ": ");
+               altGrid.add(altLabel);
+               String[] altUnits = {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")};
+               _altitudeUnitsDropdown = new JComboBox(altUnits);
+               altGrid.add(_altitudeUnitsDropdown);
+               holderPanel.add(altUnitsPanel);
+               // Horizontal speed
+               JPanel speedPanel = new JPanel();
+               GuiGridLayout speedGrid = new GuiGridLayout(speedPanel);
+               speedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.speed")));
+               JLabel speedLabel = new JLabel(I18nManager.getText("dialog.openoptions.speedunits") + ": ");
+               speedGrid.add(speedLabel);
+               _hSpeedUnitsDropdown = new JComboBox();
+               for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+                       _hSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+               }
+               speedGrid.add(_hSpeedUnitsDropdown);
+               holderPanel.add(speedPanel);
+               // Vertical speed
+               JPanel vSpeedPanel = new JPanel();
+               GuiGridLayout vSpeedGrid = new GuiGridLayout(vSpeedPanel);
+               vSpeedPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("fieldname.verticalspeed")));
+               JLabel vSpeedLabel = new JLabel(I18nManager.getText("dialog.openoptions.vertspeedunits") + ": ");
+               vSpeedGrid.add(vSpeedLabel);
+               _vSpeedUnitsDropdown = new JComboBox();
+               for (Unit spUnit : UnitSetLibrary.ALL_SPEED_UNITS) {
+                       _vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
+               }
+               vSpeedGrid.add(_vSpeedUnitsDropdown);
+               _vSpeedUpwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positiveup"));
+               JRadioButton vSpeedDownwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positivedown"));
+               ButtonGroup vSpeedDirGroup = new ButtonGroup();
+               vSpeedDirGroup.add(_vSpeedUpwardsRadio); vSpeedDirGroup.add(vSpeedDownwardsRadio);
+               vSpeedGrid.add(_vSpeedUpwardsRadio);     vSpeedGrid.add(vSpeedDownwardsRadio);
+               _vSpeedUpwardsRadio.setSelected(true);
+               holderPanel.add(vSpeedPanel);
+               thirdCard.add(holderPanel, BorderLayout.NORTH);
+
+               // Make a hider to show and hide the components according to the selected fields
+               _componentHider = new ComponentHider();
+               _componentHider.addComponent(altLabel, Field.ALTITUDE);
+               _componentHider.addComponent(_altitudeUnitsDropdown, Field.ALTITUDE);
+               _componentHider.addComponent(speedLabel, Field.SPEED);
+               _componentHider.addComponent(_hSpeedUnitsDropdown, Field.SPEED);
+               _componentHider.addComponent(vSpeedLabel, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(_vSpeedUnitsDropdown, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(_vSpeedUpwardsRadio, Field.VERTICAL_SPEED);
+               _componentHider.addComponent(vSpeedDownwardsRadio, Field.VERTICAL_SPEED);
+
+               // Add cards to the wizard
+               _wizard.addCard(firstCard);
+               _wizard.addCard(secondCard);
+               _wizard.addCard(thirdCard);
+
+               wholePanel.add(cardPanel, BorderLayout.CENTER);
                return wholePanel;
        }
 
                return wholePanel;
        }
 
@@ -471,6 +535,26 @@ public class TextFileLoader
        }
 
 
        }
 
 
+       /**
+        * Prepare the next panel to be shown, if necessary
+        */
+       private void prepareNextPanel()
+       {
+               int currPanel = _wizard.getCurrentCardIndex();
+               if (currPanel == 0) {
+                       prepareSecondPanel();
+               }
+               else if (currPanel == 1)
+               {
+                       Field[] selectedFields = _fieldTableModel.getFieldArray();
+                       // Enable / disable controls based on whether altitude / speed / vspeed fields were chosen on second panel
+                       _componentHider.enableComponents(Field.ALTITUDE, doesFieldArrayContain(selectedFields, Field.ALTITUDE));
+                       _componentHider.enableComponents(Field.SPEED, doesFieldArrayContain(selectedFields, Field.SPEED));
+                       _componentHider.enableComponents(Field.VERTICAL_SPEED, doesFieldArrayContain(selectedFields, Field.VERTICAL_SPEED));
+                       // TODO: Also check ranges of altitudes, speeds, vert speeds to show them in the third panel
+               }
+       }
+
        /**
         * Use the delimiter selected to determine the fields in the file
         * and prepare the second panel accordingly
        /**
         * Use the delimiter selected to determine the fields in the file
         * and prepare the second panel accordingly
@@ -512,14 +596,31 @@ public class TextFileLoader
                _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
 
                // Set altitude format to same as last time if available
                _fieldTable.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(fieldTypesBox));
 
                // Set altitude format to same as last time if available
-               if (_lastAltitudeFormat == Altitude.Format.METRES)
-                       _unitsDropDown.setSelectedIndex(0);
-               else if (_lastAltitudeFormat == Altitude.Format.FEET)
-                       _unitsDropDown.setSelectedIndex(1);
+               if (_lastAltitudeUnit == UnitSetLibrary.UNITS_METRES)
+                       _altitudeUnitsDropdown.setSelectedIndex(0);
+               else if (_lastAltitudeUnit == UnitSetLibrary.UNITS_FEET)
+                       _altitudeUnitsDropdown.setSelectedIndex(1);
                // no selection on field list
                selectField(-1);
        }
 
                // no selection on field list
                selectField(-1);
        }
 
+       /**
+        * See if the given array of selected fields contains the specified one
+        * @param inFields array of fields selected by user in the second panel
+        * @param inCheck field to check
+        * @return true if the field is present in the array
+        */
+       private boolean doesFieldArrayContain(Field[] inFields, Field inCheck)
+       {
+               if (inFields != null) {
+                       for (int i=0; i<inFields.length; i++) {
+                               if (inFields[i] == inCheck) { // == check ok here because it only checks for built-in fields
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
 
        /**
         * All options have been selected, so load file
 
        /**
         * All options have been selected, so load file
@@ -529,22 +630,39 @@ public class TextFileLoader
                // Save delimiter, field array and altitude format for later use
                _lastUsedDelimiter = _currentDelimiter;
                _lastSelectedFields = _fieldTableModel.getFieldArray();
                // Save delimiter, field array and altitude format for later use
                _lastUsedDelimiter = _currentDelimiter;
                _lastSelectedFields = _fieldTableModel.getFieldArray();
-               Altitude.Format altitudeFormat = Altitude.Format.METRES;
-               if (_unitsDropDown.getSelectedIndex() == 1)
-               {
-                       altitudeFormat = Altitude.Format.FEET;
-               }
-               _lastAltitudeFormat = altitudeFormat;
-               // give data to App
+               // TODO: Remember all the units selections for next load?
+               // Get the selected units for altitudes and speeds
                SourceInfo sourceInfo = new SourceInfo(_file, SourceInfo.FILE_TYPE.TEXT);
                SourceInfo sourceInfo = new SourceInfo(_file, SourceInfo.FILE_TYPE.TEXT);
+               PointCreateOptions options = new PointCreateOptions();
+               options.setAltitudeUnits(_altitudeUnitsDropdown.getSelectedIndex() == 0 ? UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET);
+               Unit hSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_hSpeedUnitsDropdown.getSelectedIndex()];
+               options.setSpeedUnits(hSpeedUnit);
+               Unit vSpeedUnit = UnitSetLibrary.ALL_SPEED_UNITS[_vSpeedUnitsDropdown.getSelectedIndex()];
+               options.setVerticalSpeedUnits(vSpeedUnit, _vSpeedUpwardsRadio.isSelected());
+
+               // give data to App
                _app.informDataLoaded(_fieldTableModel.getFieldArray(),
                _app.informDataLoaded(_fieldTableModel.getFieldArray(),
-                       _fileExtractTableModel.getData(), altitudeFormat, sourceInfo, null);
+                       _fileExtractTableModel.getData(), options, sourceInfo, null);
                // clear up file cacher
                _fileCacher.clear();
                // dispose of dialog
                _dialog.dispose();
        }
 
                // clear up file cacher
                _fileCacher.clear();
                // dispose of dialog
                _dialog.dispose();
        }
 
+       /**
+        * @return true if the inputs on the current tab are valid, user is allowed to proceed
+        */
+       private boolean isCurrentCardValid()
+       {
+               int cardIndex = _wizard.getCurrentCardIndex();
+               if (cardIndex == 1)
+               {
+                       // validate second panel
+                       return _fieldTableModel.getRowCount() > 1;
+               }
+               // all other panels are always valid
+               return true;
+       }
 
        /**
         * Make a panel with a label and a component
 
        /**
         * Make a panel with a label and a component
diff --git a/tim/prune/load/babel/AddFilterDialog.java b/tim/prune/load/babel/AddFilterDialog.java
new file mode 100644 (file)
index 0000000..7de9a3a
--- /dev/null
@@ -0,0 +1,185 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.WizardLayout;
+
+
+/**
+ * Class to manage a dialog for adding a single GPSBabel filter
+ */
+public class AddFilterDialog
+{
+       /** Parent panel to pass the filter back to */
+       private BabelFilterPanel _parentPanel = null;
+       /** Reference to parent frame */
+       private JFrame _parentFrame = null;
+       /** Main dialog */
+       private JDialog _dialog = null;
+       /** layout for dealing with cards */
+       private WizardLayout _wizard = null;
+       /** Array of filter definitions */
+       private FilterDefinition[] _filters = new FilterDefinition[4];
+       /** Finish button */
+       private JButton _finishButton = null;
+       /** back button */
+       private JButton _backButton = null;
+
+       // Selector class for one of the filter types
+       class FilterTypeListener implements ActionListener
+       {
+               private int _index = 0;
+               public FilterTypeListener(int inIndex) {_index = inIndex;}
+               public void actionPerformed(ActionEvent e) {
+                       _wizard.showCard(_index);
+                       _backButton.setEnabled(true);
+                       filterParamsChanged(); // to check parameters and enable/disable Finish button
+               }
+       }
+
+       /**
+        * Constructor
+        * @param inParent parent panel to inform of selected filter
+        * @param inParentFrame parent frame to reference for dialogs
+        */
+       public AddFilterDialog(BabelFilterPanel inParent, JFrame inParentFrame)
+       {
+               _parentPanel = inParent;
+               _parentFrame = inParentFrame;
+       }
+
+       /**
+        * Show the dialog to add a new filter
+        */
+       public void showDialog()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.addfilter.title"), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               // TODO: Initialise cards, clear entries?
+               _wizard.showFirstCard();
+               _backButton.setEnabled(false);
+               _finishButton.setEnabled(false);
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private JPanel makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout());
+
+               // card panel in the middle
+               JPanel cardPanel = new JPanel();
+               _wizard = new WizardLayout(cardPanel);
+               JPanel typesCard = new JPanel();
+               JButton discardButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.discard"));
+               discardButton.addActionListener(new FilterTypeListener(1));
+               typesCard.add(discardButton);
+               JButton simplifyButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.simplify"));
+               simplifyButton.addActionListener(new FilterTypeListener(2));
+               typesCard.add(simplifyButton);
+               JButton distanceButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.distance"));
+               distanceButton.addActionListener(new FilterTypeListener(3));
+               typesCard.add(distanceButton);
+               JButton interpButton = new JButton(I18nManager.getText("dialog.gpsbabel.filter.interpolate"));
+               interpButton.addActionListener(new FilterTypeListener(4));
+               typesCard.add(interpButton);
+
+               // discard panel
+               _filters[0] = new DiscardFilter(this);
+               // simplify panel
+               _filters[1] = new SimplifyFilter(this);
+               // distance panel
+               _filters[2] = new DistanceFilter(this);
+               // interpolate panel
+               _filters[3] = new InterpolateFilter(this);
+
+               // Add cards to the wizard
+               _wizard.addCard(typesCard);
+               _wizard.addCard(_filters[0]);
+               _wizard.addCard(_filters[1]);
+               _wizard.addCard(_filters[2]);
+               _wizard.addCard(_filters[3]);
+               dialogPanel.add(cardPanel, BorderLayout.CENTER);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+               _backButton = new JButton(I18nManager.getText("button.back"));
+               _backButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _wizard.showCard(0);
+                               _backButton.setEnabled(!_wizard.isFirstCard());
+                               _finishButton.setEnabled(false);
+                       }
+               });
+               _backButton.setEnabled(false);
+               buttonPanel.add(_backButton);
+               _finishButton = new JButton(I18nManager.getText("button.finish"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               finish();
+                       }
+               };
+               _finishButton.addActionListener(okListener);
+               _finishButton.setEnabled(false);
+               buttonPanel.add(_finishButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+
+               return dialogPanel;
+       }
+
+       /**
+        * React to changes in the filter parameters (such as enabling/disabling the ok button)
+        */
+       public void filterParamsChanged()
+       {
+               final int currCard = _wizard.getCurrentCardIndex();
+               if (currCard > 0 && currCard < 5) {
+                       _finishButton.setEnabled(_filters[currCard-1].isFilterValid());
+               }
+       }
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               // finish dialog and pass results back to the parent panel
+               final int currCard = _wizard.getCurrentCardIndex();
+               if (currCard > 0 && currCard < 5) {
+                       _parentPanel.addFilter(_filters[currCard-1].getString());
+               }
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/load/babel/BabelFilterPanel.java b/tim/prune/load/babel/BabelFilterPanel.java
new file mode 100644 (file)
index 0000000..b5b564f
--- /dev/null
@@ -0,0 +1,184 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.regex.Pattern;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.StatusIcon;
+
+/**
+ * Gui element to allow the specification of filters for GPSBabel.
+ * Used for loading from GPS and loading from file
+ */
+public class BabelFilterPanel extends JPanel
+{
+       /** Text field for entering filters manually */
+       private JTextField _filterField = null;
+       /** Icon for showing whether the value is valid for GPSBabel or not */
+       private StatusIcon _validIcon = null;
+       /** Dialog for adding a new filter */
+       private AddFilterDialog _addDialog = null;
+
+       /** Regular expression for detecting valid filter strings */
+       private static final Pattern FILTER_PATTERN
+               = Pattern.compile("(-x [a-z,\\.0-9=]+ *)+");
+
+       /**
+        * Constructor
+        * @param inParentFrame parent frame for launching popup dialog
+        */
+       public BabelFilterPanel(JFrame inParentFrame)
+       {
+               setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createTitledBorder(I18nManager.getText("dialog.gpsbabel.filters")),
+                       BorderFactory.createEmptyBorder(2, 2, 2, 2)));
+               initPanel();
+               _addDialog = new AddFilterDialog(this, inParentFrame);
+       }
+
+       /**
+        * Set up the panel with all the components inside
+        */
+       private void initPanel()
+       {
+               setLayout(new BorderLayout(4, 4));
+               // text field for the filter text
+               _filterField = new JTextField(20);
+               _filterField.addKeyListener(new KeyAdapter() {
+                       public void keyTyped(KeyEvent arg0) {
+                               SwingUtilities.invokeLater(new Runnable() {
+                                       public void run() {
+                                               checkFilter();
+                                       }
+                               });
+                       }
+               });
+               JPanel filterFieldPanel = new JPanel();
+               filterFieldPanel.setLayout(new BorderLayout(3, 3));
+               JPanel filterIconPanel = new JPanel();
+               filterIconPanel.setLayout(new BorderLayout(3, 3));
+               filterIconPanel.add(_filterField, BorderLayout.CENTER);
+               _validIcon = new StatusIcon();
+               filterIconPanel.add(_validIcon, BorderLayout.EAST);
+               filterFieldPanel.add(filterIconPanel, BorderLayout.NORTH);
+               add(filterFieldPanel, BorderLayout.CENTER);
+               // Add and clear buttons
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
+               JButton addButton = new JButton(I18nManager.getText("button.addnew"));
+               addButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               // System.out.println("Filter exists: " + hasFilter() + ", valid: " + isFilterValid());
+                               _addDialog.showDialog();
+                       }
+               });
+               buttonPanel.add(addButton);
+               buttonPanel.add(Box.createVerticalStrut(2));
+               JButton clearButton = new JButton(I18nManager.getText("button.delete"));
+               clearButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               _filterField.setText("");
+                               checkFilter();
+                       }
+               });
+               buttonPanel.add(clearButton);
+               add(buttonPanel, BorderLayout.EAST);
+       }
+
+       /**
+        * @param inFilter filter string to set (normally from config)
+        */
+       public void setFilterString(String inFilter)
+       {
+               if (inFilter != null && _filterField != null) {
+                       _filterField.setText(inFilter.trim());
+               }
+               checkFilter();
+       }
+
+       /**
+        * @return trimmed filter string, or null
+        */
+       public String getFilterString()
+       {
+               String filter = _filterField.getText();
+               if (filter != null) filter = filter.trim();
+               return filter;
+       }
+
+       /**
+        * @return true if a filter has been given (which may or may not be valid)
+        */
+       public boolean hasFilter()
+       {
+               String str = getFilterString();
+               return str != null && str.length() > 0;
+       }
+
+       /**
+        * @return true if the given filter string is valid
+        */
+       public boolean isFilterValid()
+       {
+               String str = getFilterString();
+               if (str == null) return false;
+               return FILTER_PATTERN.matcher(str).matches();
+       }
+
+       /**
+        * Called from the add filter dialog to indicate completion
+        * @param inFilter filter to add
+        */
+       public void addFilter(String inFilter)
+       {
+               if (inFilter != null)
+               {
+                       String newFilter = inFilter.trim();
+                       String currFilter = getFilterString();
+                       if (!newFilter.equals(""))
+                       {
+                               if (currFilter == null || currFilter.equals("")) {
+                                       currFilter = newFilter;
+                               }
+                               else { // append
+                                       currFilter = currFilter + " " + newFilter;
+                               }
+                       }
+                       _filterField.setText(currFilter);
+               }
+               checkFilter();
+       }
+
+       /**
+        * See if the current filter is valid or not, and update the icon accordingly
+        */
+       private void checkFilter()
+       {
+               if (hasFilter())
+               {
+                       if (isFilterValid()) {
+                               _validIcon.setStatusValid();
+                       }
+                       else {
+                               _validIcon.setStatusInvalid();
+                       }
+               }
+               else
+               {
+                       _validIcon.setStatusBlank();
+               }
+       }
+}
diff --git a/tim/prune/load/babel/DiscardFilter.java b/tim/prune/load/babel/DiscardFilter.java
new file mode 100644 (file)
index 0000000..f310031
--- /dev/null
@@ -0,0 +1,144 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Discard filter for GPSBabel
+ */
+public class DiscardFilter extends FilterDefinition
+{
+       /** Constructor */
+       public DiscardFilter(AddFilterDialog inFilterDialog)
+       {
+               super(inFilterDialog);
+               makePanelContents();
+       }
+
+       private WholeNumberField _hdopField = null;
+       private WholeNumberField _vdopField = null;
+       private JComboBox _combineDopsCombo = null;
+       private WholeNumberField _numSatsField = null;
+       private JCheckBox _noFixCheckbox = null;
+       private JCheckBox _unknownFixCheckbox = null;
+
+
+       /** @return filter name */
+       protected String getFilterName() {
+               return "discard";
+       }
+
+       /** Make the panel contents */
+       protected void makePanelContents()
+       {
+               setLayout(new BorderLayout());
+               JPanel boxPanel = new JPanel();
+               boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.intro"));
+               topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(topLabel);
+               boxPanel.add(Box.createVerticalStrut(9)); // spacer
+
+               JPanel boxPanel2 = new JPanel();
+               boxPanel2.setLayout(new BoxLayout(boxPanel2, BoxLayout.Y_AXIS));
+               // Panel for dops
+               JPanel dopPanel = new JPanel();
+               dopPanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+               );
+               dopPanel.setLayout(new GridLayout(0, 3, 4, 2));
+               dopPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.hdop"), SwingConstants.RIGHT));
+               _hdopField = new WholeNumberField(2);
+               _hdopField.addKeyListener(_paramChangeListener);
+               dopPanel.add(_hdopField);
+               _combineDopsCombo = new JComboBox(new String[] {I18nManager.getText("logic.and"), I18nManager.getText("logic.or")});
+               dopPanel.add(_combineDopsCombo);
+               dopPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.vdop"), SwingConstants.RIGHT));
+               _vdopField = new WholeNumberField(2);
+               _vdopField.addKeyListener(_paramChangeListener);
+               dopPanel.add(_vdopField);
+               boxPanel2.add(dopPanel);
+
+               // Number of satellites
+               JPanel satPanel = new JPanel();
+               satPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.discard.numsats")));
+               _numSatsField = new WholeNumberField(2);
+               _numSatsField.addKeyListener(_paramChangeListener);
+               satPanel.add(_numSatsField);
+               boxPanel2.add(satPanel);
+
+               // Checkboxes for no fix and unknown fix
+               _noFixCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsbabel.filter.discard.nofix"));
+               boxPanel2.add(_noFixCheckbox);
+               _unknownFixCheckbox = new JCheckBox(I18nManager.getText("dialog.gpsbabel.filter.discard.unknownfix"));
+               boxPanel2.add(_unknownFixCheckbox);
+               boxPanel2.add(Box.createVerticalStrut(9)); // spacer
+
+               boxPanel2.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(boxPanel2);
+               add(boxPanel, BorderLayout.NORTH);
+}
+
+       /**
+        * @return true if the filters are valid
+        */
+       public boolean isFilterValid()
+       {
+               // If values are entered, insist that they're positive (0 not valid)
+               if (_hdopField.getText() != null && _hdopField.getText().length() > 0 && _hdopField.getValue() <= 0) {return false;}
+               if (_vdopField.getText() != null && _vdopField.getText().length() > 0 && _vdopField.getValue() <= 0) {return false;}
+               if (_numSatsField.getText() != null && _numSatsField.getText().length() > 0 && _numSatsField.getValue() <= 0) {return false;}
+               // Insist that at least one value has been entered
+               return _hdopField.getValue() > 0 || _vdopField.getValue() > 0 || _numSatsField.getValue() > 0;
+       }
+
+       /**
+        * @return filter parameters as a string, or null
+        */
+       protected String getParameters()
+       {
+               if (!isFilterValid()) return null;
+               StringBuilder builder = new StringBuilder();
+               // hdop and vdop
+               final int hdop = _hdopField.getValue();
+               if (hdop > 0) {
+                       builder.append(",hdop=").append(hdop);
+               }
+               final int vdop = _vdopField.getValue();
+               if (vdop > 0)
+               {
+                       builder.append(",vdop=").append(vdop);
+                       if (hdop > 0 && _combineDopsCombo.getSelectedIndex() == 0) {
+                               builder.append(",hdopandvdop");
+                       }
+               }
+               // number of satellites
+               final int numSats = _numSatsField.getValue();
+               if (numSats > 0)
+               {
+                       builder.append(",sat=").append(numSats);
+               }
+               // checkboxes
+               if (_noFixCheckbox.isSelected()) {
+                       builder.append(",fixnone");
+               }
+               if (_unknownFixCheckbox.isSelected()) {
+                       builder.append(",fixunknown");
+               }
+               return builder.toString();
+       }
+}
diff --git a/tim/prune/load/babel/DistanceFilter.java b/tim/prune/load/babel/DistanceFilter.java
new file mode 100644 (file)
index 0000000..7554160
--- /dev/null
@@ -0,0 +1,104 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Distance filter for GPSBabel (compress by distance, or nearby points)
+ */
+public class DistanceFilter extends FilterDefinition
+{
+       /** Constructor */
+       public DistanceFilter(AddFilterDialog inFilterDialog)
+       {
+               super(inFilterDialog);
+               makePanelContents();
+       }
+
+       private DecimalNumberField _distField = null;
+       private JComboBox _distUnitsCombo = null;
+       private WholeNumberField _secondsField = null;
+
+
+       /** @return filter name */
+       protected String getFilterName() {
+               return "position";
+       }
+
+       /** Make the panel contents */
+       protected void makePanelContents()
+       {
+               setLayout(new BorderLayout());
+               JPanel boxPanel = new JPanel();
+               boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+               add(boxPanel, BorderLayout.NORTH);
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.intro"));
+               topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(topLabel);
+               boxPanel.add(Box.createVerticalStrut(18)); // spacer
+               // Main three-column grid
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.distance")));
+               _distField = new DecimalNumberField();
+               _distField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_distField);
+               _distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.metres"), I18nManager.getText("units.feet")});
+               gridPanel.add(_distUnitsCombo);
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.distance.time")));
+               _secondsField = new WholeNumberField(4);
+               _secondsField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_secondsField);
+               gridPanel.add(new JLabel(I18nManager.getText("units.seconds")));
+               gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(gridPanel);
+       }
+
+       /**
+        * @return true if the filters are valid
+        */
+       public boolean isFilterValid()
+       {
+               if (_distField.getText() == null || _distField.getText().trim().equals("")) {
+                       return false; // no distance given
+               }
+               final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+               if (timeGiven && _secondsField.getValue() <= 1) {
+                       return false; // must have a decent number of seconds
+               }
+               // check the distance
+               return (_distField.getValue() > 0.001); // no zero or negative distances allowed
+       }
+
+       /**
+        * @return filter parameters as a string, or null
+        */
+       protected String getParameters()
+       {
+               if (!isFilterValid()) return null;
+               StringBuilder builder = new StringBuilder();
+               // Get the distance
+               double dValue = _distField.getValue();
+               builder.append(",distance=").append(dValue);
+               // units of distance (miles by default)
+               builder.append(_distUnitsCombo.getSelectedIndex() == 0 ? "m" : "f"); // metres or feet
+
+               // is there a time as well?
+               final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+               if (timeGiven) {
+                       builder.append(",time=").append(_secondsField.getValue()); // no s at the end
+               }
+               return builder.toString();
+       }
+}
diff --git a/tim/prune/load/babel/FilterDefinition.java b/tim/prune/load/babel/FilterDefinition.java
new file mode 100644 (file)
index 0000000..8417908
--- /dev/null
@@ -0,0 +1,59 @@
+package tim.prune.load.babel;
+
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+/**
+ * Superclass of all the filter definition panels, to be added in the cardset
+ * of the AddFilterDialog
+ */
+public abstract class FilterDefinition extends JPanel
+{
+       /** Parent dialog to inform of parameter changes */
+       private AddFilterDialog _parentDialog = null;
+       /** Listener for key presses on the parameter entry fields */
+       protected KeyListener _paramChangeListener = null;
+
+       /**
+        * Constructor
+        */
+       public FilterDefinition(AddFilterDialog inFilterDialog)
+       {
+               _parentDialog = inFilterDialog;
+               _paramChangeListener = new KeyAdapter() {
+                       public void keyTyped(KeyEvent arg0) {
+                               SwingUtilities.invokeLater(new Runnable() {
+                                       public void run() {
+                                               _parentDialog.filterParamsChanged();
+                                       }
+                               });
+                       }
+               };
+       }
+
+       /**
+        * @return true if the filter definition is valid
+        */
+       public abstract boolean isFilterValid();
+
+       /**
+        * @return filter definition to pass to gpsbabel
+        */
+       public String getString()
+       {
+               return "-x " + getFilterName() + getParameters();
+       }
+
+       /** @return filter name */
+       protected abstract String getFilterName();
+
+       /** Construct the GUI elements and add them to the panel */
+       protected abstract void makePanelContents();
+
+       /** @return filter parameters */
+       protected abstract String getParameters();
+}
diff --git a/tim/prune/load/babel/InterpolateFilter.java b/tim/prune/load/babel/InterpolateFilter.java
new file mode 100644 (file)
index 0000000..a169846
--- /dev/null
@@ -0,0 +1,112 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Interpolate filter for GPSBabel (adding extra points, does that make it still a filter?)
+ * Very similar to the distance filter in terms of gui
+ */
+public class InterpolateFilter extends FilterDefinition
+{
+       /** Constructor */
+       public InterpolateFilter(AddFilterDialog inFilterDialog)
+       {
+               super(inFilterDialog);
+               makePanelContents();
+       }
+
+       private DecimalNumberField _distField = null;
+       private JComboBox _distUnitsCombo = null;
+       private WholeNumberField _secondsField = null;
+
+
+       /** @return filter name */
+       protected String getFilterName() {
+               return "interpolate";
+       }
+
+       /** Make the panel contents */
+       protected void makePanelContents()
+       {
+               setLayout(new BorderLayout());
+               JPanel boxPanel = new JPanel();
+               boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+               add(boxPanel, BorderLayout.NORTH);
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.intro"));
+               topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(topLabel);
+               boxPanel.add(Box.createVerticalStrut(18)); // spacer
+               // Main three-column grid
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.distance")));
+               _distField = new DecimalNumberField();
+               _distField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_distField);
+               _distUnitsCombo = new JComboBox(new String[] {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")});
+               gridPanel.add(_distUnitsCombo);
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.interpolate.time")));
+               _secondsField = new WholeNumberField(4);
+               _secondsField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_secondsField);
+               gridPanel.add(new JLabel(I18nManager.getText("units.seconds")));
+               gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(gridPanel);
+       }
+
+       /**
+        * @return true if the filters are valid
+        */
+       public boolean isFilterValid()
+       {
+               final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+               final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+               if ((!distGiven && !timeGiven) || (distGiven && timeGiven)) {
+                       return false; // either one or the other, not both
+               }
+               if (distGiven && _distField.getValue() < 0.0001) {
+                       return false; // must have a decent distance
+               }
+               if (timeGiven && _secondsField.getValue() <= 1) {
+                       return false; // must have a decent number of seconds
+               }
+               // must be ok
+               return true;
+       }
+
+       /**
+        * @return filter parameters as a string, or null
+        */
+       protected String getParameters()
+       {
+               if (!isFilterValid()) return null;
+               StringBuilder builder = new StringBuilder();
+               final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+               final boolean timeGiven = _secondsField.getText() != null && _secondsField.getText().trim().length() > 0;
+               if (distGiven)
+               {
+                       // Get the distance
+                       double dValue = _distField.getValue();
+                       builder.append(",distance=").append(dValue);
+                       // units of distance (km or miles)
+                       builder.append(_distUnitsCombo.getSelectedIndex() == 0 ? "k" : "m");
+               }
+               else if (timeGiven) {
+                       // time
+                       builder.append(",time=").append(_secondsField.getValue()); // no s at the end
+               }
+               return builder.toString();
+       }
+}
diff --git a/tim/prune/load/babel/SimplifyFilter.java b/tim/prune/load/babel/SimplifyFilter.java
new file mode 100644 (file)
index 0000000..ebbc9f4
--- /dev/null
@@ -0,0 +1,147 @@
+package tim.prune.load.babel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridLayout;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import tim.prune.I18nManager;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Simplify filter for GPSBabel
+ */
+public class SimplifyFilter extends FilterDefinition
+{
+       /** Constructor */
+       public SimplifyFilter(AddFilterDialog inFilterDialog)
+       {
+               super(inFilterDialog);
+               makePanelContents();
+       }
+
+       private WholeNumberField _maxPointsField = null;
+       private DecimalNumberField _distField = null;
+       private JComboBox _distUnitsCombo = null;
+       private JRadioButton _crossTrackRadio = null;
+       private JRadioButton _lengthRadio = null;
+       private JRadioButton _relativeRadio = null;
+
+
+       /** @return filter name */
+       protected String getFilterName() {
+               return "simplify";
+       }
+
+       /** Make the panel contents */
+       protected void makePanelContents()
+       {
+               setLayout(new BorderLayout());
+               JPanel boxPanel = new JPanel();
+               boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+               add(boxPanel, BorderLayout.NORTH);
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.intro"));
+               topLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(topLabel);
+               boxPanel.add(Box.createVerticalStrut(18)); // spacer
+               // Main three-column grid
+               JPanel gridPanel = new JPanel();
+               gridPanel.setLayout(new GridLayout(0, 3, 4, 4));
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.maxpoints")));
+               _maxPointsField = new WholeNumberField(6);
+               _maxPointsField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_maxPointsField);
+               gridPanel.add(new JLabel(" "));
+               gridPanel.add(new JLabel(I18nManager.getText("dialog.gpsbabel.filter.simplify.maxerror")));
+               _distField = new DecimalNumberField();
+               _distField.addKeyListener(_paramChangeListener);
+               gridPanel.add(_distField);
+               _distUnitsCombo = new JComboBox(new String[] {
+                       I18nManager.getText(UnitSetLibrary.UNITS_KILOMETRES.getNameKey()),
+                       I18nManager.getText(UnitSetLibrary.UNITS_MILES.getNameKey())
+               });
+               gridPanel.add(_distUnitsCombo);
+               // radio buttons
+               _crossTrackRadio = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.crosstrack"));
+               _crossTrackRadio.setSelected(true);
+               _lengthRadio     = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.length"));
+               _relativeRadio   = new JRadioButton(I18nManager.getText("dialog.gpsbabel.filter.simplify.relative"));
+               ButtonGroup radioGroup = new ButtonGroup();
+               radioGroup.add(_crossTrackRadio);
+               radioGroup.add(_lengthRadio);
+               radioGroup.add(_relativeRadio);
+               gridPanel.add(_crossTrackRadio);
+               gridPanel.add(_lengthRadio);
+               gridPanel.add(_relativeRadio);
+               gridPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               boxPanel.add(gridPanel);
+       }
+
+       /**
+        * @return true if the filters are valid
+        */
+       public boolean isFilterValid()
+       {
+               final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
+               final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+               if ((!countGiven && !distGiven) || (countGiven && distGiven)) {
+                       return false; // only one or the other allowed
+               }
+               if (countGiven && _maxPointsField.getValue() <= 1) {
+                       return false; // must have a decent max points
+               }
+               if (distGiven && _distField.getValue() <= 0.001) {
+                       return false; // no zero or negative distances allowed
+               }
+               // must be ok
+               return true;
+       }
+
+       /**
+        * @return filter parameters as a string, or null
+        */
+       protected String getParameters()
+       {
+               if (!isFilterValid()) return null;
+               StringBuilder builder = new StringBuilder();
+               // type
+               final boolean countGiven = _maxPointsField.getText() != null && _maxPointsField.getText().trim().length() > 0;
+               final boolean distGiven = _distField.getText() != null && _distField.getText().trim().length() > 0;
+               if (countGiven) {
+                       builder.append(",count=").append(_maxPointsField.getValue());
+               }
+               else if (distGiven)
+               {
+                       double dValue = 1.0;
+                       try {
+                               dValue = Double.parseDouble(_distField.getText());
+                       }
+                       catch (Exception e) {} // shouldn't happen, otherwise validation would have failed
+                       builder.append(",error=").append(dValue);
+                       // units of distance (miles by default)
+                       if (_distUnitsCombo.getSelectedIndex() == 0) {
+                               builder.append("k"); // nothing for miles
+                       }
+               }
+               // three options
+               if (_crossTrackRadio.isSelected()) {
+                       builder.append(",crosstrack"); // default, could not pass it
+               }
+               else if (_lengthRadio.isSelected()) {
+                       builder.append(",length");
+               }
+               else if (_relativeRadio.isSelected()) {
+                       builder.append(",relative");
+               }
+               return builder.toString();
+       }
+}
index 98ef9467943645182d8f6a78d9865e50ab51ba6d..3ebd4caae7658eca9e345ada1c25d202ef4fde53 100644 (file)
@@ -7,7 +7,6 @@ import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 import tim.prune.App;
 import tim.prune.I18nManager;
 import javax.xml.parsers.SAXParserFactory;
 import tim.prune.App;
 import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
@@ -55,7 +54,7 @@ public class GzipFileLoader
                                SourceInfo sourceInfo = new SourceInfo(inFile,
                                        (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
                                SourceInfo sourceInfo = new SourceInfo(inFile,
                                        (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
-                                       Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(),
+                                       null, sourceInfo, handler.getTrackNameList(),
                                        new MediaLinkInfo(inFile, handler.getLinkArray()));
                        }
                }
                                        new MediaLinkInfo(inFile, handler.getLinkArray()));
                        }
                }
index 4c4239d6b2081d2ca2799d6d38a6fe8136b659e1..783ac798ed3d0bf9a6a580cb1ee33b4cc1ebfb18 100644 (file)
@@ -10,9 +10,9 @@ import javax.xml.parsers.SAXParserFactory;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
+
 import tim.prune.App;
 import tim.prune.I18nManager;
 import tim.prune.App;
 import tim.prune.I18nManager;
-import tim.prune.data.Altitude;
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
@@ -86,7 +86,7 @@ public class XmlFileLoader extends DefaultHandler implements Runnable
                                SourceInfo sourceInfo = new SourceInfo(_file,
                                        (_handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(_handler.getFieldArray(), _handler.getDataArray(),
                                SourceInfo sourceInfo = new SourceInfo(_file,
                                        (_handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                _app.informDataLoaded(_handler.getFieldArray(), _handler.getDataArray(),
-                                       Altitude.Format.METRES, sourceInfo, _handler.getTrackNameList(),
+                                       null, sourceInfo, _handler.getTrackNameList(),
                                        new MediaLinkInfo(_handler.getLinkArray()));
                        }
                }
                                        new MediaLinkInfo(_handler.getLinkArray()));
                        }
                }
index fff0d92aa67f1587aa6e311411bc1f43caecd293..74e48f9497c6ab387dd779b2a97747511e320a85 100644 (file)
@@ -11,7 +11,6 @@ import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 import tim.prune.App;
 import javax.xml.parsers.SAXParserFactory;
 
 import tim.prune.App;
-import tim.prune.data.Altitude;
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
 import tim.prune.data.SourceInfo;
 import tim.prune.load.MediaLinkInfo;
 
@@ -69,7 +68,7 @@ public class ZipFileLoader
                                                        SourceInfo sourceInfo = new SourceInfo(inFile,
                                                                (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                                        _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
                                                        SourceInfo sourceInfo = new SourceInfo(inFile,
                                                                (handler instanceof GpxHandler?SourceInfo.FILE_TYPE.GPX:SourceInfo.FILE_TYPE.KML));
                                                        _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
-                                                               Altitude.Format.METRES, sourceInfo, handler.getTrackNameList(),
+                                                               null, sourceInfo, handler.getTrackNameList(),
                                                                new MediaLinkInfo(inFile, handler.getLinkArray()));
                                                        xmlFound = true;
                                                }
                                                                new MediaLinkInfo(inFile, handler.getLinkArray()));
                                                        xmlFound = true;
                                                }
@@ -117,7 +116,7 @@ public class ZipFileLoader
                                                {
                                                        // Send back to app
                                                        _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
                                                {
                                                        // Send back to app
                                                        _app.informDataLoaded(handler.getFieldArray(), handler.getDataArray(),
-                                                               Altitude.Format.METRES, new SourceInfo("gpsies", SourceInfo.FILE_TYPE.GPSIES),
+                                                               new SourceInfo("gpsies", SourceInfo.FILE_TYPE.GPSIES),
                                                                handler.getTrackNameList());
                                                        xmlFound = true;
                                                }
                                                                handler.getTrackNameList());
                                                        xmlFound = true;
                                                }
index 6cddea5915cbc8ebe49fd70a3faecb1131f37f63..f5379c366b93c8fe86c262696516146202a11098 100644 (file)
@@ -1,5 +1,5 @@
-GpsPrune version 14.1
-=====================
+GpsPrune version 15
+===================
 
 GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting and photo correlation.
 
 GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting and photo correlation.
@@ -17,7 +17,7 @@ Running
 =======
 
 To run GpsPrune from the jar file, simply call it from a command prompt or shell:
 =======
 
 To run GpsPrune from the jar file, simply call it from a command prompt or shell:
-   java -jar gpsprune_14.1.jar
+   java -jar gpsprune_15.jar
 
 If the jar file is saved in a different directory, you will need to include the path.
 Depending on your system settings, you may be able to click or double-click on the jar file
 
 If the jar file is saved in a different directory, you will need to include the path.
 Depending on your system settings, you may be able to click or double-click on the jar file
@@ -25,17 +25,25 @@ in a file manager window to execute it.  A shortcut, menu item, alias, desktop i
 or other link can of course be made should you wish.
 
 To specify a language other than the default, use an additional parameter, eg:
 or other link can of course be made should you wish.
 
 To specify a language other than the default, use an additional parameter, eg:
-   java -jar gpsprune_14.1.jar --lang=DE
+   java -jar gpsprune_15.jar --lang=DE
 
 
 
 
-New with version 14.1
+New with version 15
 =====================
 The following features were added since version 14:
 =====================
 The following features were added since version 14:
-  - Addition and correction of translations
-  - Correction of version number in build scripts
+  - Extend povray output using map image on base plane
+  - Export an image of the map and track at a selected zoom level
+  - Estimation of hiking times and learining of parameter values
+  - Allow altitude / speed profile to show any arbitrary field
+  - Accept files dragged and dropped onto the GpsPrune window
+  - Take account of timezone if present in track timestamps
+  - Allow timestamp exports in KML using gx extensions
+  - GPSBabel filters
+  - Improved wikipedia name lookup
+  - Allow loading of speeds and vertical speeds from text files
 
 New with version 14
 
 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
 The following features were added since version 13:
   - Dragging of existing points
   - Creation of new points by dragging the halfway point between two points
diff --git a/tim/prune/save/BaseImageConfigDialog.java b/tim/prune/save/BaseImageConfigDialog.java
new file mode 100644 (file)
index 0000000..514ebd7
--- /dev/null
@@ -0,0 +1,495 @@
+package tim.prune.save;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.File;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+import tim.prune.data.Track;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
+
+/**
+ * Dialog to let you choose the parameters for a base image
+ * (source and zoom)
+ */
+public class BaseImageConfigDialog implements Runnable
+{
+       /** Parent to notify */
+       private DataSubscriber _parent = null;
+       /** Parent dialog for position */
+       private JDialog _parentDialog = null;
+       /** Track to use for preview image */
+       private Track _track = null;
+       /** Dialog to show */
+       private JDialog _dialog = null;
+       /** Checkbox for using an image or not */
+       private JCheckBox _useImageCheckbox = null;
+       /** Panel to hold the other controls */
+       private JPanel _mainPanel = null;
+       /** Dropdown for map source */
+       private JComboBox _mapSourceDropdown = null;
+       /** Dropdown for zoom levels */
+       private JComboBox _zoomDropdown = null;
+       /** Warning label that image is incomplete */
+       private JLabel _imageIncompleteLabel = null;
+       /** Label for number of tiles found */
+       private JLabel _tilesFoundLabel = null;
+       /** Label for image size in pixels */
+       private JLabel _imageSizeLabel = null;
+       /** Image preview panel */
+       private ImagePreviewPanel _previewPanel = null;
+       /** OK button, needs to be enabled/disabled */
+       private JButton _okButton = null;
+       /** Flag for rebuilding dialog, don't bother refreshing and recalculating */
+       private boolean _rebuilding = false;
+       /** Cached values to allow cancellation of dialog */
+       private boolean        _useImage = false;
+       private int            _sourceIndex = 0;
+       private int            _zoomLevel = 0;
+
+
+       /**
+        * Constructor
+        * @param inParent parent object to notify on completion of dialog
+        * @param inParentDialog parent dialog
+        * @param inTrack track object
+        */
+       public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack)
+       {
+               _parent = inParent;
+               _parentDialog = inParentDialog;
+               _dialog = new JDialog(inParentDialog, I18nManager.getText("dialog.baseimage.title"), true);
+               _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+               _dialog.getContentPane().add(makeDialogComponents());
+               _dialog.pack();
+               _track = inTrack;
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               initDialog();
+               _dialog.setLocationRelativeTo(_parentDialog);
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Begin the function with a default of using an image
+        */
+       public void beginWithImageYes()
+       {
+               initDialog();
+               _useImageCheckbox.setSelected(true);
+               refreshDialog();
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Initialise the dialog from the cached values
+        */
+       private void initDialog()
+       {
+               _rebuilding = true;
+               _useImageCheckbox.setSelected(_useImage);
+               // Populate the dropdown of map sources from the library in case it has changed
+               _mapSourceDropdown.removeAllItems();
+               for (int i=0; i<MapSourceLibrary.getNumSources(); i++)
+               {
+                       _mapSourceDropdown.addItem(MapSourceLibrary.getSource(i).getName());
+               }
+               if (_sourceIndex < 0 || _sourceIndex >= _mapSourceDropdown.getItemCount()) {
+                       _sourceIndex = 0;
+               }
+               _mapSourceDropdown.setSelectedIndex(_sourceIndex);
+
+               // Zoom level
+               if (_useImage)
+               {
+                       for (int i=0; i<_zoomDropdown.getItemCount(); i++)
+                       {
+                               String item = _zoomDropdown.getItemAt(i).toString();
+                               try {
+                                       if (Integer.parseInt(item) == _zoomLevel) {
+                                               _zoomDropdown.setSelectedIndex(i);
+                                               break;
+                                       }
+                               }
+                               catch (NumberFormatException nfe) {}
+                       }
+               }
+               _rebuilding = false;
+               refreshDialog();
+       }
+
+       /**
+        * Update the visibility of the controls, and update the zoom dropdown based on the selected map source
+        */
+       private void refreshDialog()
+       {
+               _mainPanel.setVisible(_useImageCheckbox.isSelected());
+               // Exit if we're in the middle of something
+               if (_rebuilding) {return;}
+               int currentZoom = 0;
+               try {
+                       currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
+               }
+               catch (Exception nfe) {}
+               // Get the extent of the track so we can work out how big the images are going to be for each zoom level
+               // System.out.println("Ranges are: x=" + _track.getXRange().getRange() + ", y=" +  _track.getYRange().getRange());
+               final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
+               int zoomToSelect = -1;
+
+               _rebuilding = true;
+               _zoomDropdown.removeAllItems();
+               if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0)
+               {
+                       int currentSource = _mapSourceDropdown.getSelectedIndex();
+                       for (int i=5; i<18; i++)
+                       {
+                               // How many pixels does this give?
+                               final int zoomFactor = 1 << i;
+                               final int pixCount = (int) (xyExtent * zoomFactor * 256);
+                               if (pixCount > 100      // less than this isn't worth it
+                                       && pixCount < 4000  // don't want to run out of memory
+                                       && isZoomAvailable(i, MapSourceLibrary.getSource(currentSource)))
+                               {
+                                       _zoomDropdown.addItem("" + i);
+                                       if (i == currentZoom) {
+                                               zoomToSelect = _zoomDropdown.getItemCount() - 1;
+                                       }
+                               }
+                               // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent);
+                       }
+               }
+               _zoomDropdown.setSelectedIndex(zoomToSelect);
+               _rebuilding = false;
+
+               _okButton.setEnabled(!_useImageCheckbox.isSelected() ||
+                       (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0));
+               updateImagePreview();
+       }
+
+       /**
+        * @return true if it should be possible to use an image, false if no disk cache or cache empty
+        */
+       public static boolean isImagePossible()
+       {
+               String path = Config.getConfigString(Config.KEY_DISK_CACHE);
+               if (path != null && !path.equals(""))
+               {
+                       File cacheDir = new File(path);
+                       if (cacheDir.exists() && cacheDir.isDirectory())
+                       {
+                               // Check if there are any directories in the cache
+                               for (File subdir : cacheDir.listFiles())
+                               {
+                                       if (subdir.exists() && subdir.isDirectory()) {
+                                               return true;
+                                       }
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * See if the requested zoom level is available
+        * @param inZoom zoom level
+        * @param inSource selected map source
+        * @return true if there is a zoom directory for each of the source's layers
+        */
+       private static boolean isZoomAvailable(int inZoom, MapSource inSource)
+       {
+               if (inSource == null) {return false;}
+               String path = Config.getConfigString(Config.KEY_DISK_CACHE);
+               if (path == null || path.equals("")) {
+                       return false;
+               }
+               File cacheDir = new File(path);
+               if (!cacheDir.exists() || !cacheDir.isDirectory()) {
+                       return false;
+               }
+               // First layer
+               File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom);
+               if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) {
+                       return false;
+               }
+               // Second layer, if any
+               if (inSource.getNumLayers() > 1)
+               {
+                       File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom);
+                       if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) {
+                               return false;
+                       }
+               }
+               // must be ok
+               return true;
+       }
+
+
+       /**
+        * @return true if image has been selected
+        */
+       public boolean useImage() {
+               return _useImage;
+       }
+
+       /**
+        * @return index of selected image source
+        */
+       public int getSourceIndex() {
+               return _sourceIndex;
+       }
+
+       /**
+        * @return selected zoom level
+        */
+       public int getZoomLevel() {
+               return _zoomLevel;
+       }
+
+       /**
+        * Make the dialog components to select the options
+        * @return Component holding gui elements
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel panel = new JPanel();
+               panel.setLayout(new BorderLayout());
+               _useImageCheckbox = new JCheckBox(I18nManager.getText("dialog.baseimage.useimage"));
+               _useImageCheckbox.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
+               _useImageCheckbox.setHorizontalAlignment(JLabel.CENTER);
+               _useImageCheckbox.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               refreshDialog();
+                       }
+               });
+               panel.add(_useImageCheckbox, BorderLayout.NORTH);
+
+               // Outer panel with the grid and the map preview
+               _mainPanel = new JPanel();
+               _mainPanel.setLayout(new BorderLayout(1, 10));
+               // Central stuff with labels and dropdowns
+               JPanel controlsPanel = new JPanel();
+               controlsPanel.setLayout(new GridLayout(0, 2, 10, 4));
+               // map source
+               JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": ");
+               sourceLabel.setHorizontalAlignment(JLabel.RIGHT);
+               controlsPanel.add(sourceLabel);
+               _mapSourceDropdown = new JComboBox();
+               _mapSourceDropdown.addItem("name of map source");
+               // Add listener to dropdown to change zoom levels
+               _mapSourceDropdown.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               refreshDialog();
+                       }
+               });
+               controlsPanel.add(_mapSourceDropdown);
+               // zoom level
+               JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": ");
+               zoomLabel.setHorizontalAlignment(JLabel.RIGHT);
+               controlsPanel.add(zoomLabel);
+               _zoomDropdown = new JComboBox();
+               // Add action listener to enable ok button when zoom changed
+               _zoomDropdown.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               if (_zoomDropdown.getSelectedIndex() >= 0) {
+                                       _okButton.setEnabled(true);
+                                       updateImagePreview();
+                               }
+                       }
+               });
+               controlsPanel.add(_zoomDropdown);
+               _mainPanel.add(controlsPanel, BorderLayout.NORTH);
+
+               JPanel imagePanel = new JPanel();
+               imagePanel.setLayout(new BorderLayout(10, 1));
+               // image preview
+               _previewPanel = new ImagePreviewPanel();
+               imagePanel.add(_previewPanel, BorderLayout.CENTER);
+
+               // Label panel on right
+               JPanel labelPanel = new JPanel();
+               labelPanel.setLayout(new BorderLayout());
+               _imageIncompleteLabel = new JLabel(I18nManager.getText("dialog.baseimage.incomplete"));
+               _imageIncompleteLabel.setForeground(Color.RED);
+               _imageIncompleteLabel.setVisible(false);
+               labelPanel.add(_imageIncompleteLabel, BorderLayout.NORTH);
+               JPanel labelGridPanel = new JPanel();
+               labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4));
+               labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": "));
+               _tilesFoundLabel = new JLabel("11 / 11");
+               labelGridPanel.add(_tilesFoundLabel);
+               labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.size") + ": "));
+               _imageSizeLabel = new JLabel("1430");
+               labelGridPanel.add(_imageSizeLabel);
+               labelGridPanel.add(new JLabel(" ")); // just for spacing
+               labelPanel.add(labelGridPanel, BorderLayout.SOUTH);
+               imagePanel.add(labelPanel, BorderLayout.EAST);
+
+               _mainPanel.add(imagePanel, BorderLayout.CENTER);
+               panel.add(_mainPanel, BorderLayout.CENTER);
+
+               // OK, Cancel buttons
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               // Check values, maybe don't want to exit
+                               if (!_useImageCheckbox.isSelected()
+                                       || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0))
+                               {
+                                       storeValues();
+                                       _dialog.dispose();
+                               }
+                       }
+               });
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               panel.add(buttonPanel, BorderLayout.SOUTH);
+
+               // Listener to close dialog if escape pressed
+               KeyAdapter closer = new KeyAdapter() {
+                       public void keyReleased(KeyEvent e)
+                       {
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               _useImageCheckbox.addKeyListener(closer);
+               _mapSourceDropdown.addKeyListener(closer);
+               _zoomDropdown.addKeyListener(closer);
+               _okButton.addKeyListener(closer);
+               cancelButton.addKeyListener(closer);
+
+               return panel;
+       }
+
+       /**
+        * Use the selected settings to make a preview image and (asynchronously) update the preview panel
+        */
+       private void updateImagePreview()
+       {
+               // Clear labels
+               _imageIncompleteLabel.setVisible(false);
+               _tilesFoundLabel.setText("");
+               _imageSizeLabel.setText("");
+               if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
+                       && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)
+               {
+                       _previewPanel.startLoading();
+                       // Launch a separate thread to create an image and pass it to the preview panel
+                       new Thread(this).start();
+               }
+               else {
+                       // clear preview
+                       _previewPanel.setImage(null);
+               }
+       }
+
+       /**
+        * Store the selected details in the variables
+        */
+       private void storeValues()
+       {
+               // Store values of controls in variables
+               _useImage = _useImageCheckbox.isSelected();
+               _sourceIndex = _mapSourceDropdown.getSelectedIndex();
+               try {
+                       String zoomStr = _zoomDropdown.getSelectedItem().toString();
+                       _zoomLevel = Integer.parseInt(zoomStr);
+               }
+               catch (Exception nfe) {
+                       _zoomLevel = 0;
+               }
+               // Call parent to retrieve values
+               _parent.dataUpdated(DataSubscriber.ALL);
+       }
+
+       /**
+        * Run method for separate thread.  Uses the current dialog parameters
+        * to trigger a call to the Grouter, and pass the image to the preview panel
+        */
+       public void run()
+       {
+               // Remember the current dropdown indices, so we know whether they've changed or not
+               final int mapIndex = _mapSourceDropdown.getSelectedIndex();
+               final int zoomIndex = _zoomDropdown.getSelectedIndex();
+               if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;}
+
+               // Get the map source and zoom level
+               MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
+               int zoomLevel = 0;
+               try {
+                       zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
+               }
+               catch (Exception e) {}
+
+               // Use the Grouter to create an image (slow, blocks thread)
+               GroutedImage groutedImage = MapGrouter.createMapImage(_track, mapSource, zoomLevel);
+
+               // If the dialog hasn't changed, pass the generated image to the preview panel
+               if (_useImageCheckbox.isSelected()
+                       && _mapSourceDropdown.getSelectedIndex() == mapIndex
+                       && _zoomDropdown.getSelectedIndex() == zoomIndex
+                       && groutedImage != null)
+               {
+                       _previewPanel.setImage(groutedImage);
+                       // Set values of labels
+                       _imageIncompleteLabel.setVisible(!groutedImage.isComplete());
+                       _tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal());
+                       if (groutedImage.getImageSize() > 0) {
+                               _imageSizeLabel.setText("" + groutedImage.getImageSize());
+                       }
+                       else {
+                               _imageSizeLabel.setText("");
+                       }
+               }
+               else
+               {
+                       _previewPanel.setImage(null);
+                       // Clear labels
+                       _imageIncompleteLabel.setVisible(false);
+                       _tilesFoundLabel.setText("");
+                       _imageSizeLabel.setText("");
+               }
+       }
+
+       /**
+        * @return true if any map data has been found for the image
+        */
+       public boolean getFoundData()
+       {
+               return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound();
+       }
+}
index 020ef537ff4fa3f936c6e23cc1032bd0abb360ca..3c1a7b814f605777054862d04632f3123ed05794 100644 (file)
@@ -24,7 +24,6 @@ import tim.prune.ExternalTools;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Photo;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Photo;
@@ -244,7 +243,7 @@ public class ExifSaver implements Runnable
                        {
                                // Only look at photos which are selected and whose status has changed since load
                                photo = entry.getPhoto();
                        {
                                // Only look at photos which are selected and whose status has changed since load
                                photo = entry.getPhoto();
-                               if (photo != null && photo.getOriginalStatus() != photo.getCurrentStatus())
+                               if (photo != null && photo.isModified())
                                {
                                        // Increment counter if save successful
                                        if (savePhoto(photo, overwriteFlag, false)) {
                                {
                                        // Increment counter if save successful
                                        if (savePhoto(photo, overwriteFlag, false)) {
@@ -415,7 +414,7 @@ public class ExifSaver implements Runnable
                result[paramOffset + 3] = "-GPSLongitudeRef=" + inPoint.getLongitude().output(Coordinate.FORMAT_CARDINAL);
                // add altitude if it has it
                result[paramOffset + 4] = "-GPSAltitude="
                result[paramOffset + 3] = "-GPSLongitudeRef=" + inPoint.getLongitude().output(Coordinate.FORMAT_CARDINAL);
                // add altitude if it has it
                result[paramOffset + 4] = "-GPSAltitude="
-                + (inPoint.hasAltitude()?inPoint.getAltitude().getValue(Altitude.Format.METRES):0);
+                + (inPoint.hasAltitude()?inPoint.getAltitude().getMetricValue():0);
                result[paramOffset + 5] = "-GPSAltitudeRef='Above Sea Level'";
                // add the filename to modify
                result[paramOffset + 6] = inFile.getAbsolutePath();
                result[paramOffset + 5] = "-GPSAltitudeRef='Above Sea Level'";
                // add the filename to modify
                result[paramOffset + 6] = inFile.getAbsolutePath();
index 6be44eb80bc3eb6beb828f0270db15c75255954e..b4a73d43d86f301a464d8ab5dde1e68be189d332 100644 (file)
@@ -35,7 +35,6 @@ import tim.prune.App;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
@@ -43,6 +42,8 @@ import tim.prune.data.FieldList;
 import tim.prune.data.RecentFile;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
 import tim.prune.data.RecentFile;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.load.OneCharDocument;
 
 import tim.prune.load.GenericFileFilter;
 import tim.prune.load.OneCharDocument;
 
@@ -72,7 +73,7 @@ public class FileSaver
 
        private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
                Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
 
        private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
                Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
-       private static final Altitude.Format[] FORMAT_ALTS = {Altitude.Format.NO_FORMAT, Altitude.Format.METRES, Altitude.Format.FEET};
+       private static final Unit[] UNIT_ALTS = {null, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_FEET};
        private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
 
 
        private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
 
 
@@ -428,11 +429,11 @@ public class FileSaver
                for (int i=0; i<_coordUnitsRadios.length; i++)
                        if (_coordUnitsRadios[i].isSelected())
                                coordFormat = FORMAT_COORDS[i];
                for (int i=0; i<_coordUnitsRadios.length; i++)
                        if (_coordUnitsRadios[i].isSelected())
                                coordFormat = FORMAT_COORDS[i];
-               Altitude.Format altitudeFormat = Altitude.Format.NO_FORMAT;
+               Unit altitudeUnit = null;
                for (int i=0; i<_altitudeUnitsRadios.length; i++)
                {
                        if (_altitudeUnitsRadios[i].isSelected()) {
                for (int i=0; i<_altitudeUnitsRadios.length; i++)
                {
                        if (_altitudeUnitsRadios[i].isSelected()) {
-                               altitudeFormat = FORMAT_ALTS[i];
+                               altitudeUnit = UNIT_ALTS[i];
                        }
                }
                // Get timestamp format
                        }
                }
                // Get timestamp format
@@ -518,7 +519,7 @@ public class FileSaver
                                                        if (!firstField) {
                                                                buffer.append(delimiter);
                                                        }
                                                        if (!firstField) {
                                                                buffer.append(delimiter);
                                                        }
-                                                       saveField(buffer, point, info.getField(), coordFormat, altitudeFormat, timestampFormat);
+                                                       saveField(buffer, point, info.getField(), coordFormat, altitudeUnit, timestampFormat);
                                                        firstField = false;
                                                }
                                        }
                                                        firstField = false;
                                                }
                                        }
@@ -567,11 +568,11 @@ public class FileSaver
         * @param inPoint point object
         * @param inField field object
         * @param inCoordFormat coordinate format
         * @param inPoint point object
         * @param inField field object
         * @param inCoordFormat coordinate format
-        * @param inAltitudeFormat altitude format
+        * @param inAltitudeUnit altitude unit
         * @param inTimestampFormat timestamp format
         */
        private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
         * @param inTimestampFormat timestamp format
         */
        private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
-               int inCoordFormat, Altitude.Format inAltitudeFormat, int inTimestampFormat)
+               int inCoordFormat, Unit inAltitudeUnit, int inTimestampFormat)
        {
                // Output field according to type
                if (inField == Field.LATITUDE)
        {
                // Output field according to type
                if (inField == Field.LATITUDE)
@@ -586,7 +587,7 @@ public class FileSaver
                {
                        try
                        {
                {
                        try
                        {
-                               inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeFormat));
+                               inBuffer.append(inPoint.getAltitude().getStringValue(inAltitudeUnit));
                        }
                        catch (NullPointerException npe) {}
                }
                        }
                        catch (NullPointerException npe) {}
                }
index 19007ac920dc33c45c2522def2adb648ea1368c7..8d9df8a0c15f8a10f79fc98a0368b609b841d617 100644 (file)
@@ -35,7 +35,6 @@ import tim.prune.GpsPrune;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.AudioClip;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.AudioClip;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
@@ -45,6 +44,7 @@ import tim.prune.data.Photo;
 import tim.prune.data.RecentFile;
 import tim.prune.data.Timestamp;
 import tim.prune.data.TrackInfo;
 import tim.prune.data.RecentFile;
 import tim.prune.data.Timestamp;
 import tim.prune.data.TrackInfo;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.DialogCloser;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.save.xml.GpxCacherList;
 import tim.prune.gui.DialogCloser;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.save.xml.GpxCacherList;
@@ -530,7 +530,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                // Point has been modified - maybe it's possible to modify the source
                source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                // Point has been modified - maybe it's possible to modify the source
                source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
                source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
-               source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+               source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
                if (inPoint.isWaypoint())
                {
                source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
                if (inPoint.isWaypoint())
                {
@@ -726,7 +726,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                if (inPoint.hasAltitude())
                {
                        inWriter.write("\t\t<ele>");
                if (inPoint.hasAltitude())
                {
                        inWriter.write("\t\t<ele>");
-                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                        inWriter.write("</ele>\n");
                }
                // timestamp if available (point might have timestamp and then be turned into a waypoint)
                        inWriter.write("</ele>\n");
                }
                // timestamp if available (point might have timestamp and then be turned into a waypoint)
@@ -798,7 +798,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                if (inPoint.hasAltitude())
                {
                        inWriter.write("<ele>");
                if (inPoint.hasAltitude())
                {
                        inWriter.write("<ele>");
-                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                        inWriter.write("</ele>");
                }
                // timestamp if available (and selected)
                        inWriter.write("</ele>");
                }
                // timestamp if available (and selected)
diff --git a/tim/prune/save/GroutedImage.java b/tim/prune/save/GroutedImage.java
new file mode 100644 (file)
index 0000000..4400067
--- /dev/null
@@ -0,0 +1,111 @@
+package tim.prune.save;
+
+import java.awt.image.BufferedImage;
+
+import tim.prune.data.DoubleRange;
+
+
+/**
+ * Class to represent the result of the MapGrouter's assembly of map tiles
+ * into a single image.  Includes information about how complete the result is.
+ */
+public class GroutedImage
+{
+       private BufferedImage _image = null;
+       private int   _numTilesFound = 0;
+       private int   _numTilesMissing = 0;
+       private DoubleRange _xRange = null;
+       private DoubleRange _yRange = null;
+
+       /**
+        * Constructor
+        * @param inImage image, or null if no image possible
+        * @param inTilesUsed number of tiles used
+        * @param inTilesMissing number of tiles which could not be found
+        */
+       public GroutedImage(BufferedImage inImage, int inTilesUsed, int inTilesMissing)
+       {
+               _image = inImage;
+               _numTilesFound = inTilesUsed;
+               _numTilesMissing = inTilesMissing;
+       }
+
+       /**
+        * @return true if any content at all was found
+        */
+       public boolean isValid() {
+               return _image != null && _numTilesFound > 0;
+       }
+
+       /**
+        * @return true if all the required tiles were found
+        */
+       public boolean isComplete() {
+               return _numTilesMissing == 0;
+       }
+
+       /**
+        * @return the pixel dimensions of the result image
+        */
+       public int getImageSize()
+       {
+               if (_image == null) {return -1;}
+               return _image.getWidth();
+       }
+
+       /**
+        * @return the image object
+        */
+       public BufferedImage getImage() {
+               return _image;
+       }
+
+       /**
+        * @return the number of tiles used in the image
+        */
+       public int getNumTilesUsed() {
+               return _numTilesFound;
+       }
+
+       /**
+        * @return the number of tiles which could not be found, leaving gaps in the image
+        */
+       public int getNumTilesMissing() {
+               return _numTilesMissing;
+       }
+
+       /**
+        * @return the total number of tiles
+        */
+       public int getNumTilesTotal() {
+               return _numTilesFound + _numTilesMissing;
+       }
+
+       /**
+        * @param inRange x range of data
+        */
+       public void setXRange(DoubleRange inRange) {
+               _xRange = inRange;
+       }
+
+       /**
+        * @return x range of data
+        */
+       public DoubleRange getXRange() {
+               return _xRange;
+       }
+
+       /**
+        * @param inRange y range of data
+        */
+       public void setYRange(DoubleRange inRange) {
+               _yRange = inRange;
+       }
+
+       /**
+        * @return y range of data
+        */
+       public DoubleRange getYRange() {
+               return _yRange;
+       }
+}
diff --git a/tim/prune/save/ImageExporter.java b/tim/prune/save/ImageExporter.java
new file mode 100644 (file)
index 0000000..fabed33
--- /dev/null
@@ -0,0 +1,454 @@
+package tim.prune.save;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.ColourScheme;
+import tim.prune.config.Config;
+import tim.prune.data.DataPoint;
+import tim.prune.data.DoubleRange;
+import tim.prune.data.Track;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WholeNumberField;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
+import tim.prune.gui.map.MapUtils;
+import tim.prune.load.GenericFileFilter;
+
+/**
+ * Class to handle the exporting of map images, optionally with track data drawn on top.
+ * This allows images larger than the screen to be generated.
+ */
+public class ImageExporter extends GenericFunction implements DataSubscriber
+{
+       private JDialog   _dialog = null;
+       private JCheckBox _drawDataCheckbox = null;
+       private WholeNumberField _textScaleField = null;
+       private JLabel    _baseImageLabel = null;
+       private BaseImageConfigDialog _baseImageConfig = null;
+       private JFileChooser _fileChooser = null;
+       private JButton   _okButton = null;
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public ImageExporter(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.exportimage";
+       }
+
+       /**
+        * Begin the function by showing the input dialog
+        */
+       public void begin()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+                       _textScaleField.setValue(100);
+               }
+               // Make base image dialog too
+               if (_baseImageConfig == null) {
+                       _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _app.getTrackInfo().getTrack());
+               }
+
+               // Check if there is a cache to use
+               if (!BaseImageConfigDialog.isImagePossible())
+               {
+                       _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible");
+                       return;
+               }
+
+               updateBaseImageDetails();
+               // Show dialog
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Make the dialog components to select the export options
+        * @return Component holding gui elements
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel panel = new JPanel();
+               panel.setLayout(new BorderLayout(4, 4));
+               // Checkbox for drawing track or not
+               _drawDataCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrack"));
+               _drawDataCheckbox.setSelected(true); // draw by default
+
+               // TODO: Maybe have other controls such as line width, symbol scale factor
+               JPanel controlsPanel = new JPanel();
+               GuiGridLayout grid = new GuiGridLayout(controlsPanel);
+               grid.add(new JLabel(I18nManager.getText("dialog.exportimage.textscalepercent") + ": "));
+               _textScaleField = new WholeNumberField(3);
+               _textScaleField.setText("888");
+               grid.add(_textScaleField);
+
+               // OK, Cancel buttons
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               doExport();
+                               MapGrouter.clearMapImage();
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               MapGrouter.clearMapImage();
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               panel.add(buttonPanel, BorderLayout.SOUTH);
+
+               // Listener to close dialog if escape pressed
+               KeyAdapter closer = new KeyAdapter() {
+                       public void keyReleased(KeyEvent e)
+                       {
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                                       MapGrouter.clearMapImage();
+                               }
+                       }
+               };
+               _drawDataCheckbox.addKeyListener(closer);
+
+               // Panel for the base image
+               JPanel imagePanel = new JPanel();
+               imagePanel.setLayout(new BorderLayout(10, 4));
+               imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
+               _baseImageLabel = new JLabel("Typical sourcename");
+               imagePanel.add(_baseImageLabel, BorderLayout.CENTER);
+               JButton baseImageButton = new JButton(I18nManager.getText("button.edit"));
+               baseImageButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent event) {
+                               changeBaseImage();
+                       }
+               });
+               baseImageButton.addKeyListener(closer);
+               imagePanel.add(baseImageButton, BorderLayout.EAST);
+               imagePanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+               );
+
+               // add these panels to the holder panel
+               JPanel holderPanel = new JPanel();
+               holderPanel.setLayout(new BorderLayout(5, 5));
+               holderPanel.add(_drawDataCheckbox, BorderLayout.NORTH);
+               holderPanel.add(controlsPanel, BorderLayout.CENTER);
+               holderPanel.add(imagePanel, BorderLayout.SOUTH);
+               holderPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+               panel.add(holderPanel, BorderLayout.NORTH);
+               return panel;
+       }
+
+       /**
+        * Change the base image by calling the BaseImageConfigDialog
+        */
+       private void changeBaseImage()
+       {
+               // Check if there is a cache to use
+               if (BaseImageConfigDialog.isImagePossible())
+               {
+                       // Show new dialog to choose image details
+                       _baseImageConfig.beginWithImageYes();
+               }
+       }
+
+       /**
+        * Callback from base image config dialog
+        */
+       public void dataUpdated(byte inUpdateType)
+       {
+               updateBaseImageDetails();
+       }
+
+       /** Not required */
+       public void actionCompleted(String inMessage) {
+       }
+
+       /**
+        * Update the description label according to the selected base image details
+        */
+       private void updateBaseImageDetails()
+       {
+               String desc = null;
+               if (_baseImageConfig.useImage())
+               {
+                       MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+                       if (source != null) {
+                               desc = source.getName() + " ("
+                                       + _baseImageConfig.getZoomLevel() + ")";
+                       }
+               }
+               if (desc == null) {
+                       desc = I18nManager.getText("dialog.about.no");
+               }
+               _baseImageLabel.setText(desc);
+               _okButton.setEnabled(_baseImageConfig.useImage() && _baseImageConfig.getFoundData()
+                       && MapGrouter.isZoomLevelOk(_app.getTrackInfo().getTrack(), _baseImageConfig.getZoomLevel()));
+       }
+
+       /**
+        * Select the file and export data to it
+        */
+       private void doExport()
+       {
+               // OK pressed, so choose output file
+               _okButton.setEnabled(false);
+               if (_fileChooser == null)
+               {
+                       _fileChooser = new JFileChooser();
+                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
+                       _fileChooser.setFileFilter(new GenericFileFilter("filetype.png", new String[] {"png"}));
+                       _fileChooser.setAcceptAllFileFilterUsed(false);
+                       // start from directory in config which should be set
+                       final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
+                       if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
+               }
+
+               // Allow choose again if an existing file is selected
+               boolean chooseAgain = false;
+               do
+               {
+                       chooseAgain = false;
+                       if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
+                       {
+                               // OK pressed and file chosen
+                               File pngFile = _fileChooser.getSelectedFile();
+                               if (!pngFile.getName().toLowerCase().endsWith(".png"))
+                               {
+                                       pngFile = new File(pngFile.getAbsolutePath() + ".png");
+                               }
+                               // Check if file exists and if necessary prompt for overwrite
+                               Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
+                               if (!pngFile.exists() || JOptionPane.showOptionDialog(_parentFrame,
+                                               I18nManager.getText("dialog.save.overwrite.text"),
+                                               I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
+                                               JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
+                                       == JOptionPane.YES_OPTION)
+                               {
+                                       // Export the file
+                                       if (!exportFile(pngFile))
+                                       {
+                                               // export failed so need to choose again
+                                               chooseAgain = true;
+                                       }
+                               }
+                               else
+                               {
+                                       // overwrite cancelled so need to choose again
+                                       chooseAgain = true;
+                               }
+                       }
+               } while (chooseAgain);
+       }
+
+       /**
+        * Export the track data to the specified file
+        * @param inPngFile File object to save to
+        * @return true if successful
+        */
+       private boolean exportFile(File inPngFile)
+       {
+               // Get the image file from the grouter
+               MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+               GroutedImage baseImage = MapGrouter.getMapImage(_app.getTrackInfo().getTrack(), source,
+                       _baseImageConfig.getZoomLevel());
+               if (baseImage == null || !baseImage.isValid())
+               {
+                       _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+                       return true;
+               }
+               try
+               {
+                       if (_drawDataCheckbox.isSelected())
+                       {
+                               // Draw the track on top of this image
+                               drawData(baseImage);
+                       }
+                       // Write composite image to file
+                       if (!ImageIO.write(baseImage.getImage(), "png", inPngFile)) {
+                               _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+                               return false; // choose again - the image creation worked but the save failed
+                       }
+               }
+               catch (IOException ioe) {
+                       System.err.println("Can't write image: " + ioe.getClass().getName());
+               }
+               return true;
+       }
+
+       /**
+        * Draw the track and waypoint data from the current Track onto the given image
+        * @param inImage GroutedImage from map tiles
+        */
+       private void drawData(GroutedImage inImage)
+       {
+               // Work out x, y limits for drawing
+               DoubleRange xRange = inImage.getXRange();
+               DoubleRange yRange = inImage.getYRange();
+               int zoomFactor = 1 << _baseImageConfig.getZoomLevel();
+               Graphics g = inImage.getImage().getGraphics();
+               // TODO: Set colour, line width
+               g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+
+               // Loop over points
+               final Track track = _app.getTrackInfo().getTrack();
+               final int numPoints = track.getNumPoints();
+               int prevX = 0, prevY = 0;
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = track.getPoint(i);
+                       if (!point.isWaypoint())
+                       {
+                               double x = track.getX(i) - xRange.getMinimum();
+                               double y = track.getY(i) - yRange.getMinimum();
+                               // use zoom level to calculate pixel coords on image
+                               int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
+                               // System.out.println("Point: x=" + x + ", px=" + px + ", y=" + y + ", py=" + py);
+                               if (!point.getSegmentStart()) {
+                                       // draw from previous point to this one
+                                       g.drawLine(prevX, prevY, px, py);
+                               }
+                               // draw this point
+                               g.drawRect(px-2, py-2, 3, 3);
+                               // save coordinates
+                               prevX = px; prevY = py;
+                       }
+               }
+               // Draw waypoints
+               final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
+               g.setColor(textColour);
+               // Loop over points
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = track.getPoint(i);
+                       if (point.isWaypoint())
+                       {
+                               // draw blob for each waypoint
+                               double x = track.getX(i) - xRange.getMinimum();
+                               double y = track.getY(i) - yRange.getMinimum();
+                               // use zoom level to calculate pixel coords on image
+                               int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
+                               g.fillRect(px-3, py-3, 6, 6);
+                       }
+               }
+               // Set text size according to input
+               int fontScalePercent = _textScaleField.getValue();
+               if (fontScalePercent > 10 && fontScalePercent <= 999)
+               {
+                       Font gFont = g.getFont();
+                       g.setFont(gFont.deriveFont((float) (gFont.getSize() * 0.01 * fontScalePercent)));
+               }
+               FontMetrics fm = g.getFontMetrics();
+               final int nameHeight = fm.getHeight();
+               final int imageSize = inImage.getImageSize();
+
+               // Loop over points again, draw photo points
+               final Color photoColour = Config.getColourScheme().getColour(ColourScheme.IDX_SECONDARY);
+               g.setColor(photoColour);
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = track.getPoint(i);
+                       if (point.hasMedia())
+                       {
+                               // draw blob for each photo
+                               double x = track.getX(i) - xRange.getMinimum();
+                               double y = track.getY(i) - yRange.getMinimum();
+                               // use zoom level to calculate pixel coords on image
+                               int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
+                               g.fillRect(px-3, py-3, 6, 6);
+                       }
+               }
+
+               // Loop over points again, now draw names for waypoints
+               g.setColor(textColour);
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = track.getPoint(i);
+                       if (point.isWaypoint())
+                       {
+                               double x = track.getX(i) - xRange.getMinimum();
+                               double y = track.getY(i) - yRange.getMinimum();
+                               int px = (int) (x * zoomFactor * 256), py = (int) (y * zoomFactor * 256);
+
+                               // Figure out where to draw waypoint name so it doesn't obscure track
+                               String waypointName = point.getWaypointName();
+                               int nameWidth = fm.stringWidth(waypointName);
+                               boolean drawnName = false;
+                               // Make arrays for coordinates right left up down
+                               int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
+                               int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
+                               for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
+                               {
+                                       // Shift arrays for coordinates right left up down
+                                       nameXs[0] += 2; nameXs[1] -= 2;
+                                       nameYs[2] -= 2; nameYs[3] += 2;
+                                       // Check each direction in turn right left up down
+                                       for (int a=0; a<4; a++)
+                                       {
+                                               if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < imageSize
+                                                       && nameYs[a] < imageSize && (nameYs[a] - nameHeight) > 0
+                                                       && !MapUtils.overlapsPoints(inImage.getImage(), nameXs[a], nameYs[a],
+                                                               nameWidth, nameHeight, textColour))
+                                               {
+                                                       // Found a rectangle to fit - draw name here and quit
+                                                       g.drawString(waypointName, nameXs[a], nameYs[a]);
+                                                       drawnName = true;
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               // Maybe draw note at the bottom, export from GpsPrune?  Filename?
+               // Note: Differences from main map: No mapPosition (modifying position and visible points),
+               //       no selection, no opacities, maybe different scale/text factors
+       }
+}
diff --git a/tim/prune/save/ImagePreviewPanel.java b/tim/prune/save/ImagePreviewPanel.java
new file mode 100644 (file)
index 0000000..6f4ac88
--- /dev/null
@@ -0,0 +1,98 @@
+package tim.prune.save;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import javax.swing.JPanel;
+
+import tim.prune.I18nManager;
+
+/**
+ * GUI component to display an image preview for the POV export
+ * and for the image export
+ */
+public class ImagePreviewPanel extends JPanel
+{
+       /** Base image */
+       private GroutedImage _baseImage = null;
+       /** Loading flag */
+       private boolean _loading = false;
+
+       /** String to show before image is loaded */
+       private static final String LOADING_STRING = I18nManager.getText("details.photo.loading") + " ...";
+       /** Colour to use if there aren't any tiles at all (same as colour of POV export without image) */
+       private static final Color EMPTY_IMAGE_COLOUR = new Color(0.5f, 0.75f, 0.8f);
+
+
+       /** Set the base image */
+       public void setImage(GroutedImage inImage)
+       {
+               _baseImage = inImage;
+               _loading = false;
+               repaint();
+       }
+
+       /** Inform that a load is starting */
+       public void startLoading()
+       {
+               _baseImage = null;
+               _loading = true;
+               repaint();
+       }
+
+       /** Get minimum size */
+       public Dimension getMinimumSize() {
+               return new Dimension(200, 200);
+       }
+       /** Preferred size */
+       public Dimension getPreferredSize() {
+               return getMinimumSize();
+       }
+
+       /**
+        * Override paint method
+        * @see javax.swing.JComponent#paint(java.awt.Graphics)
+        */
+       public void paint(Graphics inG)
+       {
+               super.paint(inG);
+               if (_loading)
+               {
+                       inG.setColor(Color.BLACK);
+                       inG.drawString(LOADING_STRING, 10, 30);
+               }
+               else if (_baseImage != null)
+               {
+                       final int width = getWidth();
+                       final int height = getHeight();
+                       final int previewSize = Math.min(width, height);
+                       if (previewSize > 1)
+                       {
+                               if (_baseImage.isValid())
+                               {
+                                       inG.drawImage(_baseImage.getImage(),
+                                               (width-previewSize)/2, (height-previewSize)/2, previewSize, previewSize, this);
+                               }
+                               else
+                               {
+                                       // No content found at all
+                                       inG.setColor(EMPTY_IMAGE_COLOUR);
+                                       inG.fillRect((width-previewSize)/2, (height-previewSize)/2, previewSize, previewSize);
+                               }
+                               // draw frame around it to make it more obvious
+                               inG.setColor(Color.BLACK);
+                               inG.drawRect((width-previewSize)/2, (height-previewSize)/2, previewSize-1, previewSize-1);
+                       }
+               }
+       }
+
+       /**
+        * @return true if there is an image to use and it contains map tiles
+        */
+       public boolean getTilesFound()
+       {
+               return _baseImage != null && _baseImage.isValid() && _baseImage.getImageSize() > 1
+                       && _baseImage.getNumTilesUsed() > 0;
+       }
+}
index e1cc1516ac77e4e8c9da1967f748bd3ee78cd89c..6fbf7498bf4651e3445e00ae7f566703bc615cd1 100644 (file)
@@ -23,6 +23,7 @@ import javax.imageio.ImageIO;
 import javax.imageio.ImageWriter;
 import javax.swing.Box;
 import javax.swing.BoxLayout;
 import javax.imageio.ImageWriter;
 import javax.swing.Box;
 import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
@@ -32,6 +33,7 @@ import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JProgressBar;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JProgressBar;
+import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
 
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
 
@@ -41,17 +43,19 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.ColourUtils;
 import tim.prune.config.Config;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.ColourUtils;
 import tim.prune.config.Config;
-import tim.prune.data.Altitude;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.RecentFile;
 import tim.prune.data.Coordinate;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.RecentFile;
+import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.ColourChooser;
 import tim.prune.gui.ColourPatch;
 import tim.prune.gui.DialogCloser;
 import tim.prune.gui.ImageUtils;
 import tim.prune.gui.ColourChooser;
 import tim.prune.gui.ColourPatch;
 import tim.prune.gui.DialogCloser;
 import tim.prune.gui.ImageUtils;
+import tim.prune.gui.WholeNumberField;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.save.xml.XmlUtils;
 
 import tim.prune.load.GenericFileFilter;
 import tim.prune.save.xml.XmlUtils;
 
@@ -66,9 +70,12 @@ public class KmlExporter extends GenericFunction implements Runnable
        private JDialog _dialog = null;
        private JTextField _descriptionField = null;
        private PointTypeSelector _pointTypeSelector = null;
        private JDialog _dialog = null;
        private JTextField _descriptionField = null;
        private PointTypeSelector _pointTypeSelector = null;
+       private JRadioButton _gxExtensionsRadio = null;
        private JCheckBox _altitudesCheckbox = null;
        private JCheckBox _kmzCheckbox = null;
        private JCheckBox _exportImagesCheckbox = null;
        private JCheckBox _altitudesCheckbox = null;
        private JCheckBox _kmzCheckbox = null;
        private JCheckBox _exportImagesCheckbox = null;
+       private JLabel _imageSizeLabel = null;
+       private WholeNumberField _imageSizeField = null;
        private ColourPatch _colourPatch = null;
        private JLabel _progressLabel = null;
        private JProgressBar _progressBar = null;
        private ColourPatch _colourPatch = null;
        private JLabel _progressLabel = null;
        private JProgressBar _progressBar = null;
@@ -83,7 +90,6 @@ public class KmlExporter extends GenericFunction implements Runnable
        private static final String KML_FILENAME_IN_KMZ = "doc.kml";
        // Default width and height of thumbnail images in Kmz
        private static final int DEFAULT_THUMBNAIL_WIDTH = 240;
        private static final String KML_FILENAME_IN_KMZ = "doc.kml";
        // Default width and height of thumbnail images in Kmz
        private static final int DEFAULT_THUMBNAIL_WIDTH = 240;
-       private static final int DEFAULT_THUMBNAIL_HEIGHT = 240;
        // Default track colour
        private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red
 
        // Default track colour
        private static final Color DEFAULT_TRACK_COLOUR = new Color(204, 0, 0); // red
 
@@ -119,6 +125,8 @@ public class KmlExporter extends GenericFunction implements Runnable
                        _dialog.pack();
                        _colourChooser = new ColourChooser(_dialog);
                }
                        _dialog.pack();
                        _colourChooser = new ColourChooser(_dialog);
                }
+               // Fill in image size from config
+               _imageSizeField.setValue(Config.getConfigInt(Config.KEY_KMZ_IMAGE_SIZE));
                enableCheckboxes();
                _descriptionField.setEnabled(true);
                _okButton.setEnabled(true);
                enableCheckboxes();
                _descriptionField.setEnabled(true);
                _okButton.setEnabled(true);
@@ -169,19 +177,31 @@ public class KmlExporter extends GenericFunction implements Runnable
                colourPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.trackcolour")));
                colourPanel.add(_colourPatch);
                mainPanel.add(colourPanel);
                colourPanel.add(new JLabel(I18nManager.getText("dialog.exportkml.trackcolour")));
                colourPanel.add(_colourPatch);
                mainPanel.add(colourPanel);
+               // Pair of radio buttons for standard/extended KML
+               JRadioButton standardKmlRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.standardkml"));
+               _gxExtensionsRadio = new JRadioButton(I18nManager.getText("dialog.exportkml.extendedkml"));
+               ButtonGroup bGroup = new ButtonGroup();
+               bGroup.add(standardKmlRadio); bGroup.add(_gxExtensionsRadio);
+               JPanel radioPanel = new JPanel();
+               radioPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 1));
+               radioPanel.add(standardKmlRadio);
+               radioPanel.add(_gxExtensionsRadio);
+               standardKmlRadio.setSelected(true);
+               mainPanel.add(radioPanel);
                // Checkbox for altitude export
                _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude"));
                _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _altitudesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_altitudesCheckbox);
                // Checkbox for altitude export
                _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude"));
                _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _altitudesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(_altitudesCheckbox);
+
                // Checkboxes for kmz export and image export
                _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
                _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _kmzCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                // Checkboxes for kmz export and image export
                _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz"));
                _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _kmzCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+               // enable image checkbox if kmz activated
                _kmzCheckbox.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                _kmzCheckbox.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
-                               // enable image checkbox if kmz activated
                                enableCheckboxes();
                        }
                });
                                enableCheckboxes();
                        }
                });
@@ -189,7 +209,23 @@ public class KmlExporter extends GenericFunction implements Runnable
                _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
                _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _exportImagesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
                _exportImagesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.exportimages"));
                _exportImagesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT);
                _exportImagesCheckbox.setAlignmentX(Component.CENTER_ALIGNMENT);
+               // enable image size fields if image checkbox changes
+               _exportImagesCheckbox.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent arg0) {
+                               enableImageSizeFields();
+                       }
+               });
                mainPanel.add(_exportImagesCheckbox);
                mainPanel.add(_exportImagesCheckbox);
+               // Panel for the image size
+               JPanel imageSizePanel = new JPanel();
+               imageSizePanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+               _imageSizeLabel = new JLabel(I18nManager.getText("dialog.exportkml.imagesize"));
+               _imageSizeLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+               imageSizePanel.add(_imageSizeLabel);
+               _imageSizeField = new WholeNumberField(4);
+               imageSizePanel.add(_imageSizeField);
+               mainPanel.add(imageSizePanel);
+
                mainPanel.add(Box.createVerticalStrut(10));
                _progressLabel = new JLabel("...");
                _progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
                mainPanel.add(Box.createVerticalStrut(10));
                _progressLabel = new JLabel("...");
                _progressLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
@@ -237,9 +273,26 @@ public class KmlExporter extends GenericFunction implements Runnable
                boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
                _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
                _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
                boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0;
                _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected());
                _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected());
+               enableImageSizeFields();
+       }
+
+       /**
+        * Enable and disable the image size fields according to the checkboxes
+        */
+       private void enableImageSizeFields()
+       {
+               boolean exportImages = _exportImagesCheckbox.isEnabled() && _exportImagesCheckbox.isSelected();
+               _imageSizeField.setEnabled(exportImages);
+               _imageSizeLabel.setEnabled(exportImages);
        }
 
 
        }
 
 
+       /**
+        * @return true if using gx extensions for kml export
+        */
+       private boolean useGxExtensions() {
+               return _gxExtensionsRadio.isSelected();
+       }
        /**
         * Start the export process based on the input parameters
         */
        /**
         * Start the export process based on the input parameters
         */
@@ -325,11 +378,6 @@ public class KmlExporter extends GenericFunction implements Runnable
                boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
                _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
 
                boolean exportImages = exportToKmz && _exportImagesCheckbox.isSelected();
                _progressBar.setMaximum(exportImages?getNumPhotosToExport():1);
 
-               // Determine photo thumbnail size from config
-               int thumbWidth = Config.getConfigInt(Config.KEY_KMZ_IMAGE_WIDTH);
-               if (thumbWidth < DEFAULT_THUMBNAIL_WIDTH) {thumbWidth = DEFAULT_THUMBNAIL_WIDTH;}
-               int thumbHeight = Config.getConfigInt(Config.KEY_KMZ_IMAGE_HEIGHT);
-               if (thumbHeight < DEFAULT_THUMBNAIL_HEIGHT) {thumbHeight = DEFAULT_THUMBNAIL_HEIGHT;}
                // Create array for image dimensions in case it's required
                _imageDimensions = new Dimension[_track.getNumPoints()];
 
                // Create array for image dimensions in case it's required
                _imageDimensions = new Dimension[_track.getNumPoints()];
 
@@ -350,9 +398,14 @@ public class KmlExporter extends GenericFunction implements Runnable
                                // Export images into zip file too if requested
                                if (exportImages)
                                {
                                // Export images into zip file too if requested
                                if (exportImages)
                                {
+                                       // Get entered value for image size, store in config
+                                       int thumbSize = _imageSizeField.getValue();
+                                       if (thumbSize < DEFAULT_THUMBNAIL_WIDTH) {thumbSize = DEFAULT_THUMBNAIL_WIDTH;}
+                                       Config.setConfigInt(Config.KEY_KMZ_IMAGE_SIZE, thumbSize);
+
                                        // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
                                        // This is done first so that photo sizes are known for later
                                        // Create thumbnails of each photo in turn and add to zip as images/image<n>.jpg
                                        // This is done first so that photo sizes are known for later
-                                       exportThumbnails(zipOutputStream, thumbWidth, thumbHeight);
+                                       exportThumbnails(zipOutputStream, thumbSize);
                                }
                                writer = new OutputStreamWriter(zipOutputStream);
                                // Make an entry in the zip file for the kml file
                                }
                                writer = new OutputStreamWriter(zipOutputStream);
                                // Make an entry in the zip file for the kml file
@@ -420,8 +473,14 @@ public class KmlExporter extends GenericFunction implements Runnable
                boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
                boolean writeAudios = _pointTypeSelector.getAudiopointsSelected();
                boolean justSelection = _pointTypeSelector.getJustSelection();
                boolean writePhotos = _pointTypeSelector.getPhotopointsSelected();
                boolean writeAudios = _pointTypeSelector.getAudiopointsSelected();
                boolean justSelection = _pointTypeSelector.getJustSelection();
-               inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n<Folder>\n");
-               inWriter.write("\t<name>");
+               // Define xml header (depending on whether extensions are used or not)
+               if (useGxExtensions()) {
+                       inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n");
+               }
+               else {
+                       inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
+               }
+               inWriter.write("<Folder>\n\t<name>");
                if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
                {
                        inWriter.write(_descriptionField.getText());
                if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals(""))
                {
                        inWriter.write(_descriptionField.getText());
@@ -492,51 +551,168 @@ public class KmlExporter extends GenericFunction implements Runnable
                // Make a line for the track, if there is one
                if (hasTrackpoints && writeTrack)
                {
                // Make a line for the track, if there is one
                if (hasTrackpoints && writeTrack)
                {
-                       // Set up strings for start and end of track segment
-                       String trackStart = "\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
-                               + "\t\t\t\t<color>cc" + reverse(ColourUtils.makeHexCode(_colourPatch.getBackground())) + "</color>\n"
-                               + "\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
-                               + "\t\t\t<PolyStyle><color>33cc0000</color></PolyStyle>\n"
-                               + "\t\t</Style>\n\t\t<LineString>\n";
-                       if (absoluteAltitudes) {
-                               trackStart += "\t\t\t<extrude>1</extrude>\n\t\t\t<altitudeMode>absolute</altitudeMode>\n";
+                       boolean useGxExtensions = _gxExtensionsRadio.isSelected();
+                       if (useGxExtensions)
+                       {
+                               // Write track using the Google Extensions to KML including gx:Track
+                               numSaved += writeGxTrack(inWriter, absoluteAltitudes, selStart, selEnd);
                        }
                        else {
                        }
                        else {
-                               trackStart += "\t\t\t<altitudeMode>clampToGround</altitudeMode>\n";
+                               // Write track using standard KML
+                               numSaved += writeStandardTrack(inWriter, absoluteAltitudes, selStart, selEnd);
                        }
                        }
-                       trackStart += "\t\t\t<coordinates>";
-                       String trackEnd = "\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>";
-
-                       // Start segment
-                       inWriter.write(trackStart);
-                       // Loop over track points
-                       boolean firstTrackpoint = true;
-                       for (i=0; i<numPoints; i++)
+               }
+               inWriter.write("</Folder>\n</kml>");
+               return numSaved;
+       }
+
+
+       /**
+        * Write out the track using standard KML LineString tag
+        * @param inWriter writer object to write to
+        * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground
+        * @param inSelStart start index of selection, or -1 if whole track
+        * @param inSelEnd     end index of selection, or -1 if whole track
+        * @return number of track points written
+        */
+       private int writeStandardTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart,
+               int inSelEnd)
+       throws IOException
+       {
+               int numSaved = 0;
+               // Set up strings for start and end of track segment
+               String trackStart = "\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
+                       + "\t\t\t\t<color>cc" + reverse(ColourUtils.makeHexCode(_colourPatch.getBackground())) + "</color>\n"
+                       + "\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+                       + "\t\t</Style>\n\t\t<LineString>\n";
+               if (inAbsoluteAltitudes) {
+                       trackStart += "\t\t\t<extrude>1</extrude>\n\t\t\t<altitudeMode>absolute</altitudeMode>\n";
+               }
+               else {
+                       trackStart += "\t\t\t<altitudeMode>clampToGround</altitudeMode>\n";
+               }
+               trackStart += "\t\t\t<coordinates>";
+               String trackEnd = "\t\t\t</coordinates>\n\t\t</LineString>\n\t</Placemark>";
+
+               boolean justSelection = _pointTypeSelector.getJustSelection();
+
+               // Start segment
+               inWriter.write(trackStart);
+               // Loop over track points
+               boolean firstTrackpoint = true;
+               final int numPoints = _track.getNumPoints();
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = _track.getPoint(i);
+                       boolean writeCurrentPoint = !justSelection || (i>=inSelStart && i<=inSelEnd);
+                       if (!point.isWaypoint() && writeCurrentPoint)
                        {
                        {
-                               point = _track.getPoint(i);
-                               boolean writeCurrentPoint = !justSelection || (i>=selStart && i<=selEnd);
-                               if (!point.isWaypoint() && writeCurrentPoint)
+                               // start new track segment if necessary
+                               if (point.getSegmentStart() && !firstTrackpoint) {
+                                       inWriter.write(trackEnd);
+                                       inWriter.write(trackStart);
+                               }
+                               if (point.getPhoto() == null)
                                {
                                {
-                                       // start new track segment if necessary
-                                       if (point.getSegmentStart() && !firstTrackpoint) {
-                                               inWriter.write(trackEnd);
-                                               inWriter.write(trackStart);
+                                       exportTrackpoint(point, inWriter);
+                                       numSaved++;
+                                       firstTrackpoint = false;
+                               }
+                       }
+               }
+               // end segment
+               inWriter.write(trackEnd);
+               return numSaved;
+       }
+
+
+       /**
+        * Write out the track using Google's KML Extensions such as gx:Track
+        * @param inWriter writer object to write to
+        * @param inAbsoluteAltitudes true to use absolute altitudes, false to clamp to ground
+        * @param inSelStart start index of selection, or -1 if whole track
+        * @param inSelEnd     end index of selection, or -1 if whole track
+        * @return number of track points written
+        */
+       private int writeGxTrack(OutputStreamWriter inWriter, boolean inAbsoluteAltitudes, int inSelStart,
+               int inSelEnd)
+       throws IOException
+       {
+               int numSaved = 0;
+               // Set up strings for start and end of track segment
+               String trackStart = "\t<Placemark>\n\t\t<name>track</name>\n\t\t<Style>\n\t\t\t<LineStyle>\n"
+                       + "\t\t\t\t<color>cc" + reverse(ColourUtils.makeHexCode(_colourPatch.getBackground())) + "</color>\n"
+                       + "\t\t\t\t<width>4</width>\n\t\t\t</LineStyle>\n"
+                       + "\t\t</Style>\n\t\t<gx:Track>\n";
+               if (inAbsoluteAltitudes) {
+                       trackStart += "\t\t\t<extrude>1</extrude>\n\t\t\t<altitudeMode>absolute</altitudeMode>\n";
+               }
+               else {
+                       trackStart += "\t\t\t<altitudeMode>clampToGround</altitudeMode>\n";
+               }
+               String trackEnd = "\n\t\t</gx:Track>\n\t</Placemark>\n";
+
+               boolean justSelection = _pointTypeSelector.getJustSelection();
+
+               // Start segment
+               inWriter.write(trackStart);
+               StringBuilder whenList = new StringBuilder();
+               StringBuilder coordList = new StringBuilder();
+
+               // Loop over track points
+               boolean firstTrackpoint = true;
+               final int numPoints = _track.getNumPoints();
+               for (int i=0; i<numPoints; i++)
+               {
+                       DataPoint point = _track.getPoint(i);
+                       boolean writeCurrentPoint = !justSelection || (i>=inSelStart && i<=inSelEnd);
+                       if (!point.isWaypoint() && writeCurrentPoint)
+                       {
+                               // start new track segment if necessary
+                               if (point.getSegmentStart() && !firstTrackpoint)
+                               {
+                                       inWriter.write(whenList.toString());
+                                       inWriter.write('\n');
+                                       inWriter.write(coordList.toString());
+                                       inWriter.write('\n');
+                                       inWriter.write(trackEnd);
+                                       whenList.setLength(0); coordList.setLength(0);
+                                       inWriter.write(trackStart);
+                               }
+                               if (point.getPhoto() == null)
+                               {
+                                       // Add timestamp (if any) to the list
+                                       whenList.append("<when>");
+                                       if (point.hasTimestamp()) {
+                                               whenList.append(point.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
                                        }
                                        }
-                                       if (point.getPhoto() == null)
-                                       {
-                                               exportTrackpoint(point, inWriter);
-                                               numSaved++;
-                                               firstTrackpoint = false;
+                                       whenList.append("</when>\n");
+                                       // Add coordinates to the list
+                                       coordList.append("<gx:coord>");
+                                       coordList.append(point.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' ');
+                                       coordList.append(point.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT)).append(' ');
+                                       if (point.hasAltitude()) {
+                                               coordList.append("" + point.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                                        }
                                        }
+                                       else {
+                                               coordList.append('0');
+                                       }
+                                       coordList.append("</gx:coord>\n");
+                                       numSaved++;
+                                       firstTrackpoint = false;
                                }
                        }
                                }
                        }
-                       // end segment
-                       inWriter.write(trackEnd);
                }
                }
-               inWriter.write("</Folder>\n</kml>");
+               // end segment
+               inWriter.write(whenList.toString());
+               inWriter.write('\n');
+               inWriter.write(coordList.toString());
+               inWriter.write('\n');
+               inWriter.write(trackEnd);
                return numSaved;
        }
 
                return numSaved;
        }
 
+
        /**
         * Reverse the hex code for the colours for KML's stupid backwards format
         * @param inCode colour code rrggbb
        /**
         * Reverse the hex code for the colours for KML's stupid backwards format
         * @param inCode colour code rrggbb
@@ -652,7 +828,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                inWriter.write(',');
                // Altitude if point has one
                if (inPoint.hasAltitude()) {
                inWriter.write(',');
                // Altitude if point has one
                if (inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                }
                else {
                        inWriter.write('0');
                }
                else {
                        inWriter.write('0');
@@ -674,7 +850,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                // Altitude if point has one
                inWriter.write(',');
                if (inPoint.hasAltitude()) {
                // Altitude if point has one
                inWriter.write(',');
                if (inPoint.hasAltitude()) {
-                       inWriter.write("" + inPoint.getAltitude().getStringValue(Altitude.Format.METRES));
+                       inWriter.write("" + inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
                }
                else {
                        inWriter.write('0');
                }
                else {
                        inWriter.write('0');
@@ -686,10 +862,9 @@ public class KmlExporter extends GenericFunction implements Runnable
        /**
         * Loop through the photos and create thumbnails
         * @param inZipStream zip stream to save image files to
        /**
         * Loop through the photos and create thumbnails
         * @param inZipStream zip stream to save image files to
-        * @param inThumbWidth thumbnail width
-        * @param inThumbHeight thumbnail height
+        * @param inThumbSize thumbnail size
         */
         */
-       private void exportThumbnails(ZipOutputStream inZipStream, int inThumbWidth, int inThumbHeight)
+       private void exportThumbnails(ZipOutputStream inZipStream, int inThumbSize)
        throws IOException
        {
                // set up image writer
        throws IOException
        {
                // set up image writer
@@ -724,9 +899,9 @@ public class KmlExporter extends GenericFunction implements Runnable
                                // Load image and write to outstream
                                ImageIcon icon = point.getPhoto().createImageIcon();
 
                                // Load image and write to outstream
                                ImageIcon icon = point.getPhoto().createImageIcon();
 
-                               // Scale image to required size TODO: should it also be smoothed, or only if it's smaller than a certain size?
+                               // Scale image to required size (not smoothed)
                                BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(),
                                BufferedImage bufferedImage = ImageUtils.rotateImage(icon.getImage(),
-                                       inThumbWidth, inThumbHeight, point.getPhoto().getRotationDegrees());
+                                       inThumbSize, inThumbSize, point.getPhoto().getRotationDegrees());
                                // Store image dimensions so that it doesn't have to be calculated again for the points
                                _imageDimensions[i] = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight());
 
                                // Store image dimensions so that it doesn't have to be calculated again for the points
                                _imageDimensions[i] = new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight());
 
diff --git a/tim/prune/save/MapGrouter.java b/tim/prune/save/MapGrouter.java
new file mode 100644 (file)
index 0000000..65e23c0
--- /dev/null
@@ -0,0 +1,139 @@
+package tim.prune.save;
+
+import tim.prune.config.Config;
+import tim.prune.data.DoubleRange;
+import tim.prune.data.Track;
+import tim.prune.data.TrackExtents;
+import tim.prune.gui.map.DiskTileCacher;
+import tim.prune.gui.map.MapSource;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+
+/**
+ * Class to handle the sticking together (grouting) of map tiles
+ * to create a single map image for the current track
+ */
+public abstract class MapGrouter
+{
+       /** The most recently produced image */
+       private static GroutedImage _lastGroutedImage = null;
+
+       /**
+        * Clear the last image, it's not needed any more
+        */
+       public static void clearMapImage() {
+               _lastGroutedImage = null;
+       }
+
+       /**
+        * Grout the required map tiles together according to the track's extent
+        * @param inTrack track object
+        * @param inMapSource map source to use (may have one or two layers)
+        * @param inZoom selected zoom level
+        * @return grouted image, or null if no image could be created
+        */
+       public static GroutedImage createMapImage(Track inTrack, MapSource inMapSource, int inZoom)
+       {
+               // Get the extents of the track including a standard (10%) border around the data
+               TrackExtents extents = new TrackExtents(inTrack);
+               extents.applySquareBorder();
+               DoubleRange xRange = extents.getXRange();
+               DoubleRange yRange = extents.getYRange();
+
+               // Get path to disk cache
+               final String cachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
+               // Work out which tiles are required
+               final int zoomFactor = 1 << inZoom;
+               final int minTileX = (int) (xRange.getMinimum() * zoomFactor);
+               final int maxTileX = (int) (xRange.getMaximum() * zoomFactor);
+               final int minTileY = (int) (yRange.getMinimum() * zoomFactor);
+               final int maxTileY = (int) (yRange.getMaximum() * zoomFactor);
+
+               // Work out how big the final image will be, create a BufferedImage
+               final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
+               if (pixCount < 2) {return null;}
+               BufferedImage resultImage = new BufferedImage(pixCount, pixCount, BufferedImage.TYPE_INT_RGB);
+               Graphics g = resultImage.getGraphics();
+               g.setColor(Color.WHITE);
+               g.fillRect(0, 0, pixCount, pixCount);
+               // Work out where to start drawing the tiles on the image
+               int xOffset = (int) ((minTileX - xRange.getMinimum() * zoomFactor) * 256);
+
+               int numTilesUsed = 0;
+               int numTilesMissing = 0;
+               // Loop over the tiles
+               for (int x = minTileX; x <= maxTileX; x++)
+               {
+                       int yOffset = (int) ((minTileY - yRange.getMinimum() * zoomFactor) * 256);
+                       for (int y = minTileY; y <= maxTileY; y++)
+                       {
+                               for (int layer=0; layer < inMapSource.getNumLayers(); layer++)
+                               {
+                                       Image tile = DiskTileCacher.getTile(cachePath, inMapSource.makeFilePath(layer, inZoom, x, y), false);
+                                       if (tile != null)
+                                       {
+                                               // Wait until tile is available (loaded asynchronously)
+                                               while (tile.getWidth(null) < 0) {
+                                                       try {
+                                                               Thread.sleep(100);
+                                                       }
+                                                       catch (InterruptedException ie) {}
+                                               }
+                                               // work out where to copy it to, paint it
+                                               // System.out.println("Painting tile " + x + "," + y + " at " + xOffset + "," + yOffset);
+                                               numTilesUsed++;
+                                               g.drawImage(tile, xOffset, yOffset, null);
+                                       }
+                                       else numTilesMissing++;
+                               }
+                               yOffset += 256;
+                       }
+                       xOffset += 256;
+               }
+               // Get rid of the image if it's empty
+               if (numTilesUsed == 0) {
+                       resultImage = null;
+               }
+               // Store the xy limits in the GroutedImage to make it easier to draw on top
+               GroutedImage result = new GroutedImage(resultImage, numTilesUsed, numTilesMissing);
+               result.setXRange(xRange);
+               result.setYRange(yRange);
+               return result;
+       }
+
+       /**
+        * Get the grouted map image, using the previously-created one if available
+        * @param inTrack track object
+        * @param inMapSource map source to use (may have one or two layers)
+        * @param inZoom selected zoom level
+        * @return grouted image, or null if no image could be created
+        */
+       public static GroutedImage getMapImage(Track inTrack, MapSource inMapSource, int inZoom)
+       {
+               if (_lastGroutedImage == null) {
+                       _lastGroutedImage = createMapImage(inTrack, inMapSource, inZoom);
+               }
+               return _lastGroutedImage;
+       }
+
+       /**
+        * @param inTrack track object
+        * @param inZoom selected zoom level
+        * @return true if the image size is acceptable
+        */
+       public static boolean isZoomLevelOk(Track inTrack, int inZoom)
+       {
+               // Get the extents of the track including a standard (10%) border around the data
+               TrackExtents extents = new TrackExtents(inTrack);
+               extents.applySquareBorder();
+
+               // Work out how big the final image will be
+               final int zoomFactor = 1 << inZoom;
+               final int pixCount = (int) (extents.getXRange().getRange() * zoomFactor * 256);
+               return pixCount > 2 && pixCount < 4000;
+       }
+}
index ee30841d6d779f7790b95006a58a75eda7759433..ba6605853f560b62eb3df3b3aabdfdd5fe7e8c70 100644 (file)
@@ -12,7 +12,9 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Iterator;
 
 import java.util.ArrayList;
 import java.util.Iterator;
 
+import javax.imageio.ImageIO;
 import javax.swing.BorderFactory;
 import javax.swing.BorderFactory;
+import javax.swing.Box;
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
 import javax.swing.JButton;
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
 import javax.swing.JButton;
@@ -24,8 +26,10 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
 import javax.swing.JRadioButton;
 import javax.swing.JTextField;
 import javax.swing.SwingConstants;
+import javax.swing.border.EtchedBorder;
 
 import tim.prune.App;
 
 import tim.prune.App;
+import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
@@ -33,14 +37,16 @@ import tim.prune.data.NumberUtils;
 import tim.prune.data.Track;
 import tim.prune.function.Export3dFunction;
 import tim.prune.gui.DialogCloser;
 import tim.prune.data.Track;
 import tim.prune.function.Export3dFunction;
 import tim.prune.gui.DialogCloser;
+import tim.prune.gui.map.MapSource;
+import tim.prune.gui.map.MapSourceLibrary;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.load.GenericFileFilter;
-import tim.prune.threedee.LineDialog;
 import tim.prune.threedee.ThreeDModel;
 
 /**
  * Class to export a 3d scene of the track to a specified Pov file
 import tim.prune.threedee.ThreeDModel;
 
 /**
  * Class to export a 3d scene of the track to a specified Pov file
+ * Note: Subscriber interface only used for callback from image config dialog
  */
  */
-public class PovExporter extends Export3dFunction
+public class PovExporter extends Export3dFunction implements DataSubscriber
 {
        private Track _track = null;
        private JDialog _dialog = null;
 {
        private Track _track = null;
        private JDialog _dialog = null;
@@ -49,9 +55,13 @@ public class PovExporter extends Export3dFunction
        private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
        private JTextField _fontName = null, _altitudeFactorField = null;
        private JRadioButton _ballsAndSticksButton = null;
        private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
        private JTextField _fontName = null, _altitudeFactorField = null;
        private JRadioButton _ballsAndSticksButton = null;
+       private JLabel _baseImageLabel = null;
+       private JButton _baseImageButton = null;
+       private BaseImageConfigDialog _baseImageConfig = null;
 
        // defaults
        private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
 
        // defaults
        private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
+       private static final double MODEL_SCALE_FACTOR = 20.0;
        private static final String DEFAULT_FONT_FILE = "crystal.ttf";
 
 
        private static final String DEFAULT_FONT_FILE = "crystal.ttf";
 
 
@@ -84,10 +94,10 @@ public class PovExporter extends Export3dFunction
                double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ);
                if (cameraDist > 0.0)
                {
                double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ);
                if (cameraDist > 0.0)
                {
-                       _cameraX = NumberUtils.formatNumber(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
-                       _cameraY = NumberUtils.formatNumber(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+                       _cameraX = NumberUtils.formatNumberUk(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+                       _cameraY = NumberUtils.formatNumberUk(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
                        // Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system!
                        // Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system!
-                       _cameraZ = NumberUtils.formatNumber(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
+                       _cameraZ = NumberUtils.formatNumberUk(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
                }
        }
 
                }
        }
 
@@ -104,12 +114,18 @@ public class PovExporter extends Export3dFunction
                        _dialog.setLocationRelativeTo(_parentFrame);
                        _dialog.getContentPane().add(makeDialogComponents());
                }
                        _dialog.setLocationRelativeTo(_parentFrame);
                        _dialog.getContentPane().add(makeDialogComponents());
                }
+               // Make base image dialog
+               if (_baseImageConfig == null)
+               {
+                       _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _track);
+               }
 
                // Set angles
                _cameraXField.setText(_cameraX);
                _cameraYField.setText(_cameraY);
                _cameraZField.setText(_cameraZ);
                _altitudeFactorField.setText("" + _altFactor);
 
                // Set angles
                _cameraXField.setText(_cameraX);
                _cameraYField.setText(_cameraY);
                _cameraZField.setText(_cameraZ);
                _altitudeFactorField.setText("" + _altFactor);
+               updateBaseImageDetails();
                // Show dialog
                _dialog.pack();
                _dialog.setVisible(true);
                // Show dialog
                _dialog.pack();
                _dialog.setVisible(true);
@@ -123,7 +139,7 @@ public class PovExporter extends Export3dFunction
        private Component makeDialogComponents()
        {
                JPanel panel = new JPanel();
        private Component makeDialogComponents()
        {
                JPanel panel = new JPanel();
-               panel.setLayout(new BorderLayout());
+               panel.setLayout(new BorderLayout(4, 4));
                JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportpov.text"));
                introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
                panel.add(introLabel, BorderLayout.NORTH);
                JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportpov.text"));
                introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
                panel.add(introLabel, BorderLayout.NORTH);
@@ -135,6 +151,7 @@ public class PovExporter extends Export3dFunction
                        public void actionPerformed(ActionEvent e)
                        {
                                doExport();
                        public void actionPerformed(ActionEvent e)
                        {
                                doExport();
+                               MapGrouter.clearMapImage();
                                _dialog.dispose();
                        }
                });
                                _dialog.dispose();
                        }
                });
@@ -143,6 +160,7 @@ public class PovExporter extends Export3dFunction
                cancelButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
                cancelButton.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e)
                        {
+                               MapGrouter.clearMapImage();
                                _dialog.dispose();
                        }
                });
                                _dialog.dispose();
                        }
                });
@@ -205,37 +223,78 @@ public class PovExporter extends Export3dFunction
                group.add(_ballsAndSticksButton); group.add(tubesButton);
                stylePanel.add(radioPanel);
 
                group.add(_ballsAndSticksButton); group.add(tubesButton);
                stylePanel.add(radioPanel);
 
-               // add this grid to the holder panel
+               // Panel for the base image
+               JPanel imagePanel = new JPanel();
+               imagePanel.setLayout(new BorderLayout(10, 4));
+               imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
+               _baseImageLabel = new JLabel("Typical sourcename");
+               imagePanel.add(_baseImageLabel, BorderLayout.CENTER);
+               _baseImageButton = new JButton(I18nManager.getText("button.edit"));
+               _baseImageButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent event) {
+                               changeBaseImage();
+                       }
+               });
+               imagePanel.add(_baseImageButton, BorderLayout.EAST);
+               // Put these image controls inside a holder panel with an outline
+               JPanel imageHolderPanel = new JPanel();
+               imageHolderPanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+               );
+               imageHolderPanel.setLayout(new BorderLayout());
+               imageHolderPanel.add(imagePanel, BorderLayout.NORTH);
+
+               // add these panels to the holder panel
                JPanel holderPanel = new JPanel();
                holderPanel.setLayout(new BorderLayout(5, 5));
                JPanel boxPanel = new JPanel();
                boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
                boxPanel.add(centralPanel);
                JPanel holderPanel = new JPanel();
                holderPanel.setLayout(new BorderLayout(5, 5));
                JPanel boxPanel = new JPanel();
                boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
                boxPanel.add(centralPanel);
+               boxPanel.add(Box.createVerticalStrut(4));
                boxPanel.add(stylePanel);
                boxPanel.add(stylePanel);
+               boxPanel.add(Box.createVerticalStrut(4));
+               boxPanel.add(imageHolderPanel);
                holderPanel.add(boxPanel, BorderLayout.CENTER);
 
                holderPanel.add(boxPanel, BorderLayout.CENTER);
 
-               // show lines button
-               JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
-               showLinesButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               // Need to scale model to find lines
-                               ThreeDModel model = new ThreeDModel(_track);
-                               model.scale();
-                               double[] latLines = model.getLatitudeLines();
-                               double[] lonLines = model.getLongitudeLines();
-                               LineDialog dialog = new LineDialog(_parentFrame, latLines, lonLines);
-                               dialog.showDialog();
-                       }
-               });
-               JPanel flowPanel = new JPanel();
-               flowPanel.setLayout(new FlowLayout());
-               flowPanel.add(showLinesButton);
-               holderPanel.add(flowPanel, BorderLayout.EAST);
                panel.add(holderPanel, BorderLayout.CENTER);
                return panel;
        }
 
                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
 
        /**
         * Select the file and export data to it
@@ -267,25 +326,27 @@ public class PovExporter extends Export3dFunction
                        if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                        {
                                // OK pressed and file chosen
                        if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
                        {
                                // OK pressed and file chosen
-                               File file = _fileChooser.getSelectedFile();
-                               if (!file.getName().toLowerCase().endsWith(".pov"))
+                               File povFile = _fileChooser.getSelectedFile();
+                               if (!povFile.getName().toLowerCase().endsWith(".pov"))
                                {
                                {
-                                       file = new File(file.getAbsolutePath() + ".pov");
+                                       povFile = new File(povFile.getAbsolutePath() + ".pov");
                                }
                                }
-                               // Check if file exists and if necessary prompt for overwrite
+                               final int nameLen = povFile.getName().length() - 4;
+                               final File imageFile = new File(povFile.getParentFile(), povFile.getName().substring(0, nameLen) + "_base.png");
+                               final boolean imageExists = _baseImageConfig.useImage() && imageFile.exists();
+                               // Check if files exist and if necessary prompt for overwrite
                                Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
                                Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
-                               if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
+                               if ((!povFile.exists() && !imageExists) || JOptionPane.showOptionDialog(_parentFrame,
                                                I18nManager.getText("dialog.save.overwrite.text"),
                                                I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
                                                JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
                                        == JOptionPane.YES_OPTION)
                                {
                                        // Export the file
                                                I18nManager.getText("dialog.save.overwrite.text"),
                                                I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
                                                JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
                                        == JOptionPane.YES_OPTION)
                                {
                                        // Export the file
-                                       if (exportFile(file))
+                                       if (exportFile(povFile, imageFile))
                                        {
                                        {
-                                               // file saved
-                                               // Store directory in config for later
-                                               Config.setConfigString(Config.KEY_TRACK_DIR, file.getParentFile().getAbsolutePath());
+                                               // file saved - store directory in config for later
+                                               Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath());
                                        }
                                        else
                                        {
                                        }
                                        else
                                        {
@@ -305,10 +366,11 @@ public class PovExporter extends Export3dFunction
 
        /**
         * Export the track data to the specified file
 
        /**
         * Export the track data to the specified file
-        * @param inFile File object to save to
+        * @param inPovFile File object to save pov file to
+        * @param inImageFile file object to save image to
         * @return true if successful
         */
         * @return true if successful
         */
-       private boolean exportFile(File inFile)
+       private boolean exportFile(File inPovFile, File inImageFile)
        {
                FileWriter writer = null;
                // find out the line separator for this system
        {
                FileWriter writer = null;
                // find out the line separator for this system
@@ -317,6 +379,7 @@ public class PovExporter extends Export3dFunction
                {
                        // create and scale model
                        ThreeDModel model = new ThreeDModel(_track);
                {
                        // create and scale model
                        ThreeDModel model = new ThreeDModel(_track);
+                       model.setModelSize(MODEL_SCALE_FACTOR);
                        try
                        {
                                // try to use given altitude cap
                        try
                        {
                                // try to use given altitude cap
@@ -328,12 +391,28 @@ public class PovExporter extends Export3dFunction
                        }
                        model.scale();
 
                        }
                        model.scale();
 
-                       // Create file and write basics
-                       writer = new FileWriter(inFile);
-                       writeStartOfFile(writer, model.getModelSize(), lineSeparator);
+                       boolean useImage = _baseImageConfig.useImage();
+                       if (useImage)
+                       {
+                               // Get base image from grouter
+                               MapSource mapSource = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
+                               GroutedImage baseImage = MapGrouter.getMapImage(_track, mapSource, _baseImageConfig.getZoomLevel());
+                               try
+                               {
+                                       useImage = ImageIO.write(baseImage.getImage(), "png", inImageFile);
+                               }
+                               catch (IOException ioe) {
+                                       System.err.println("Can't write image: " + ioe.getClass().getName());
+                                       useImage = false;
+                               }
+                               if (!useImage) {
+                                       _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
+                               }
+                       }
 
 
-                       // write out lat/long lines using model
-                       writeLatLongLines(writer, model, lineSeparator);
+                       // Create file and write basics
+                       writer = new FileWriter(inPovFile);
+                       writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null);
 
                        // write out points
                        if (_ballsAndSticksButton.isSelected()) {
 
                        // write out points
                        if (_ballsAndSticksButton.isSelected()) {
@@ -346,7 +425,7 @@ public class PovExporter extends Export3dFunction
                        // everything worked
                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
                                 + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
                        // everything worked
                        UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
                                 + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
-                                + " " + inFile.getAbsolutePath());
+                                + " " + inPovFile.getAbsolutePath());
                        return true;
                }
                catch (IOException ioe)
                        return true;
                }
                catch (IOException ioe)
@@ -370,11 +449,11 @@ public class PovExporter extends Export3dFunction
        /**
         * Write the start of the Pov file, including base plane and lights
         * @param inWriter Writer to use for writing file
        /**
         * Write the start of the Pov file, including base plane and lights
         * @param inWriter Writer to use for writing file
-        * @param inModelSize model size
         * @param inLineSeparator line separator to use
         * @param inLineSeparator line separator to use
+        * @param inImageFile image file to reference (or null if none)
         * @throws IOException on file writing error
         */
         * @throws IOException on file writing error
         */
-       private void writeStartOfFile(FileWriter inWriter, double inModelSize, String inLineSeparator)
+       private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile)
        throws IOException
        {
                inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/");
        throws IOException
        {
                inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/");
@@ -389,6 +468,20 @@ public class PovExporter extends Export3dFunction
                else {
                        Config.setConfigString(Config.KEY_POVRAY_FONT, fontPath);
                }
                else {
                        Config.setConfigString(Config.KEY_POVRAY_FONT, fontPath);
                }
+
+               // Make the definition of the base plane depending on whether there's an image or not
+               final boolean useImage = (inImageFile != null);
+               final String boxDefinition = (inImageFile == null ?
+                       "   <-10.0, -0.15, -10.0>," + inLineSeparator
+                               + "   <10.0, 0.15, 10.0>" + inLineSeparator
+                               + "   pigment { color rgb <0.5 0.75 0.8> }"
+                       :
+                       "   <0, 0, 0>, <1, 1, 0.001>" + inLineSeparator
+                               + "   pigment {image_map { png \"" + inImageFile.getName() + "\" map_type 0 interpolate 2 once } }" + inLineSeparator
+                               + "   scale 20.0 rotate <90, 0, 0>" + inLineSeparator
+                               + "   translate <-10.0, 0, -10.0>");
+               // TODO: Maybe could use the same geometry for the imageless case, would simplify code a bit
+
                // Set up output
                String[] outputLines = {
                  "global_settings { ambient_light rgb <4, 4, 4> }", "",
                // Set up output
                String[] outputLines = {
                  "global_settings { ambient_light rgb <4, 4, 4> }", "",
@@ -401,27 +494,15 @@ public class PovExporter extends Export3dFunction
                  "}", "",
                // global declares
                  "// Global declares",
                  "}", "",
                // global declares
                  "// Global declares",
-                 "#declare lat_line =",
-                 "  cylinder {",
-                 "   <-" + inModelSize + ", 0.1, 0>,",
-                 "   <" + inModelSize + ", 0.1, 0>,",
-                 "   0.1            // Radius",
-                 "   pigment { color rgb <0.5 0.5 0.5> }",
-                 "  }",
-                 "#declare lon_line =",
-                 "  cylinder {",
-                 "   <0, 0.1, -" + inModelSize + ">,",
-                 "   <0, 0.1, " + inModelSize + ">,",
-                 "   0.1            // Radius",
-                 "   pigment { color rgb <0.5 0.5 0.5> }",
-                 "  }",
                  "#declare point_rod =",
                  "  cylinder {",
                  "   <0, 0, 0>,",
                  "   <0, 1, 0>,",
                  "   0.15",
                  "   open",
                  "#declare point_rod =",
                  "  cylinder {",
                  "   <0, 0, 0>,",
                  "   <0, 1, 0>,",
                  "   0.15",
                  "   open",
-                 "   pigment { color rgb <0.5 0.5 0.5> }",
+                 "   texture {",
+                 "    pigment { color rgb <0.5 0.5 0.5> }",
+                 useImage ? "   } no_shadow" : "   }",
                  "  }", "",
                  // MAYBE: Export rods to POV?  How to store in data?
                  "#declare waypoint_sphere =",
                  "  }", "",
                  // MAYBE: Export rods to POV?  How to store in data?
                  "#declare waypoint_sphere =",
@@ -430,7 +511,7 @@ public class PovExporter extends Export3dFunction
                  "    texture {",
                  "       pigment {color rgb <0.1 0.1 1.0>}",
                  "       finish { phong 1 }",
                  "    texture {",
                  "       pigment {color rgb <0.1 0.1 1.0>}",
                  "       finish { phong 1 }",
-                 "    }",
+                 useImage ? "    } no_shadow" : "    }",
                  "  }",
                  "#declare track_sphere0 =",
                  "  sphere {",
                  "  }",
                  "#declare track_sphere0 =",
                  "  sphere {",
@@ -491,31 +572,29 @@ public class PovExporter extends Export3dFunction
                  "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
                  "// Base plane",
                  "box {",
                  "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
                  "// Base plane",
                  "box {",
-                 "   <-" + inModelSize + ", -0.15, -" + inModelSize + ">,  // Near lower left corner",
-                 "   <" + inModelSize + ", 0.15, " + inModelSize + ">   // Far upper right corner",
-                 "   pigment { color rgb <0.5 0.75 0.8> }",
+                 boxDefinition,
                  "}", "",
                // write cardinals
                  "// Cardinal letters N,S,E,W",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
                  "}", "",
                // write cardinals
                  "// Cardinal letters N,S,E,W",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
-                 "  translate <0, 0.2, " + inModelSize + ">",
+                 "  translate <0, 0.2, 10.0>",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
-                 "  translate <0, 0.2, -" + inModelSize + ">",
+                 "  translate <0, 0.2, -10.0>",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
-                 "  translate <" + (inModelSize * 0.97) + ", 0.2, 0>",
+                 "  translate <9.7, 0.2, 0>",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
                  "}",
                  "text {",
                  "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0",
                  "  pigment { color rgb <1 1 1> }",
-                 "  translate <-" + (inModelSize * 1.03) + ", 0.2, 0>",
+                 "  translate <-10.3, 0.2, 0>",
                  "}", "",
                  // MAYBE: Light positions should relate to model size
                  "// lights",
                  "}", "",
                  // MAYBE: Light positions should relate to model size
                  "// lights",
@@ -534,36 +613,6 @@ public class PovExporter extends Export3dFunction
        }
 
 
        }
 
 
-       /**
-        * Write out all the lat and long lines to the file
-        * @param inWriter Writer to use for writing file
-        * @param inModel model object for getting lat/long lines
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private static void writeLatLongLines(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
-       throws IOException
-       {
-               inWriter.write("// Latitude and longitude lines:");
-               inWriter.write(inLineSeparator);
-               int numlines = inModel.getLatitudeLines().length;
-               for (int i=0; i<numlines; i++)
-               {
-                       // write cylinder to file
-                       inWriter.write("object { lat_line translate <0, 0, " + inModel.getScaledLatitudeLine(i) + "> }");
-                       inWriter.write(inLineSeparator);
-               }
-               numlines = inModel.getLongitudeLines().length;
-               for (int i=0; i<numlines; i++)
-               {
-                       // write cylinder to file
-                       inWriter.write("object { lon_line translate <" + inModel.getScaledLongitudeLine(i) + ", 0, 0> }");
-                       inWriter.write(inLineSeparator);
-               }
-               inWriter.write(inLineSeparator);
-       }
-
-
        /**
         * Write out all the data points to the file in the balls-and-sticks style
         * @param inWriter Writer to use for writing file
        /**
         * Write out all the data points to the file in the balls-and-sticks style
         * @param inWriter Writer to use for writing file
@@ -829,4 +878,16 @@ public class PovExporter extends Export3dFunction
                }
                return segmentList;
        }
                }
                return segmentList;
        }
+
+       /**
+        * Callback from base image config dialog
+        */
+       public void dataUpdated(byte inUpdateType)
+       {
+               updateBaseImageDetails();
+       }
+
+       /** Not required */
+       public void actionCompleted(String inMessage) {
+       }
 }
 }
index 4a86c5b5472c27f0b1b7e201ba658194b793b1f6..33f42f95a75bbbb3d4d671edc44b76a824c481ca 100644 (file)
@@ -47,7 +47,7 @@ public class SvgExporter extends Export3dFunction
        private JTextField _phiField = null, _thetaField = null;
        private JTextField _altitudeFactorField = null;
        private JCheckBox _gradientsCheckbox = null;
        private JTextField _phiField = null, _thetaField = null;
        private JTextField _altitudeFactorField = null;
        private JCheckBox _gradientsCheckbox = null;
-       private static double _scaleFactor = 1.0;
+       private static final double _scaleFactor = 200.0;
 
 
        /**
 
 
        /**
@@ -265,16 +265,15 @@ public class SvgExporter extends Export3dFunction
                        }
                        catch (NumberFormatException nfe) {}
                        model.scale();
                        }
                        catch (NumberFormatException nfe) {}
                        model.scale();
-                       _scaleFactor = 200 / model.getModelSize();
 
                        boolean useGradients = _gradientsCheckbox.isSelected();
 
                        // Create file and write basics
                        writer = new FileWriter(inFile);
                        writeStartOfFile(writer, useGradients, lineSeparator);
 
                        boolean useGradients = _gradientsCheckbox.isSelected();
 
                        // Create file and write basics
                        writer = new FileWriter(inFile);
                        writeStartOfFile(writer, useGradients, lineSeparator);
-                       writeBasePlane(writer, model.getModelSize(), lineSeparator);
+                       writeBasePlane(writer, lineSeparator);
                        // write out cardinal letters NESW
                        // write out cardinal letters NESW
-                       writeCardinals(writer, model.getModelSize(), lineSeparator);
+                       writeCardinals(writer, lineSeparator);
 
                        // write out points
                        writeDataPoints(writer, model, useGradients, lineSeparator);
 
                        // write out points
                        writeDataPoints(writer, model, useGradients, lineSeparator);
@@ -342,18 +341,17 @@ public class SvgExporter extends Export3dFunction
        /**
         * Write the base plane
         * @param inWriter Writer to use for writing file
        /**
         * Write the base plane
         * @param inWriter Writer to use for writing file
-        * @param inModelSize model size
         * @param inLineSeparator line separator to use
         * @throws IOException on file writing error
         */
         * @param inLineSeparator line separator to use
         * @throws IOException on file writing error
         */
-       private void writeBasePlane(FileWriter inWriter, double inModelSize, String inLineSeparator)
+       private void writeBasePlane(FileWriter inWriter, String inLineSeparator)
        throws IOException
        {
                // Use model size and camera angles to draw path for base rectangle (using 3d transform)
        throws IOException
        {
                // Use model size and camera angles to draw path for base rectangle (using 3d transform)
-               int[] coords1 = convertCoordinates(-inModelSize, -inModelSize, 0);
-               int[] coords2 = convertCoordinates(inModelSize, -inModelSize, 0);
-               int[] coords3 = convertCoordinates(inModelSize, inModelSize, 0);
-               int[] coords4 = convertCoordinates(-inModelSize, inModelSize, 0);
+               int[] coords1 = convertCoordinates(-1.0, -1.0, 0);
+               int[] coords2 = convertCoordinates(1.0, -1.0, 0);
+               int[] coords3 = convertCoordinates(1.0, 1.0, 0);
+               int[] coords4 = convertCoordinates(-1.0, 1.0, 0);
                final String corners = "M " + coords1[0] + "," + coords1[1]
                        + " L " + coords2[0] + "," + coords2[1]
                        + " L " + coords3[0] + "," + coords3[1]
                final String corners = "M " + coords1[0] + "," + coords1[1]
                        + " L " + coords2[0] + "," + coords2[1]
                        + " L " + coords3[0] + "," + coords3[1]
@@ -365,21 +363,20 @@ public class SvgExporter extends Export3dFunction
        /**
         * Write the cardinal letters NESW
         * @param inWriter Writer to use for writing file
        /**
         * Write the cardinal letters NESW
         * @param inWriter Writer to use for writing file
-        * @param inModelSize model size
         * @param inLineSeparator line separator to use
         * @throws IOException on file writing error
         */
         * @param inLineSeparator line separator to use
         * @throws IOException on file writing error
         */
-       private void writeCardinals(FileWriter inWriter, double inModelSize, String inLineSeparator)
+       private void writeCardinals(FileWriter inWriter, String inLineSeparator)
        throws IOException
        {
                // Use model size and camera angles to calculate positions
        throws IOException
        {
                // Use model size and camera angles to calculate positions
-               int[] coordsN = convertCoordinates(0, inModelSize, 0);
+               int[] coordsN = convertCoordinates(0, 1.0, 0);
                writeCardinal(inWriter, coordsN[0], coordsN[1], "cardinal.n", inLineSeparator);
                writeCardinal(inWriter, coordsN[0], coordsN[1], "cardinal.n", inLineSeparator);
-               int[] coordsE = convertCoordinates(inModelSize, 0, 0);
+               int[] coordsE = convertCoordinates(1.0, 0, 0);
                writeCardinal(inWriter, coordsE[0], coordsE[1], "cardinal.e", inLineSeparator);
                writeCardinal(inWriter, coordsE[0], coordsE[1], "cardinal.e", inLineSeparator);
-               int[] coordsS = convertCoordinates(0, -inModelSize, 0);
+               int[] coordsS = convertCoordinates(0, -1.0, 0);
                writeCardinal(inWriter, coordsS[0], coordsS[1], "cardinal.s", inLineSeparator);
                writeCardinal(inWriter, coordsS[0], coordsS[1], "cardinal.s", inLineSeparator);
-               int[] coordsW = convertCoordinates(-inModelSize, 0, 0);
+               int[] coordsW = convertCoordinates(-1.0, 0, 0);
                writeCardinal(inWriter, coordsW[0], coordsW[1], "cardinal.w", inLineSeparator);
        }
 
                writeCardinal(inWriter, coordsW[0], coordsW[1], "cardinal.w", inLineSeparator);
        }
 
index 1671607d5e9c1a417b4b75e878aef5aa9465eec4..cfac9afabf1798d87e33f199371ed98ba62fb743 100644 (file)
@@ -58,7 +58,7 @@ public class Java3DWindow implements ThreeDWindow
        private JFrame _frame = null;
        private ThreeDModel _model = null;
        private OrbitBehavior _orbit = null;
        private JFrame _frame = null;
        private ThreeDModel _model = null;
        private OrbitBehavior _orbit = null;
-       private double _altFactor = 50.0;
+       private double _altFactor = 5.0;
 
        /** only prompt about big track size once */
        private static boolean TRACK_SIZE_WARNING_GIVEN = false;
 
        /** only prompt about big track size once */
        private static boolean TRACK_SIZE_WARNING_GIVEN = false;
@@ -68,6 +68,7 @@ public class Java3DWindow implements ThreeDWindow
        private static final double INITIAL_X_ROTATION = 15.0;
        private static final String CARDINALS_FONT = "Arial";
        private static final int MAX_TRACK_SIZE = 2500; // threshold for warning
        private static final double INITIAL_X_ROTATION = 15.0;
        private static final String CARDINALS_FONT = "Arial";
        private static final int MAX_TRACK_SIZE = 2500; // threshold for warning
+       private static final double MODEL_SCALE_FACTOR = 20.0;
 
 
        /**
 
 
        /**
@@ -127,9 +128,8 @@ public class Java3DWindow implements ThreeDWindow
                Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
                if (_track.getNumPoints() > MAX_TRACK_SIZE && !TRACK_SIZE_WARNING_GIVEN)
                {
                Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
                if (_track.getNumPoints() > MAX_TRACK_SIZE && !TRACK_SIZE_WARNING_GIVEN)
                {
-                       // FIXME: Change text reference from exportpov to java3d
                        if (JOptionPane.showOptionDialog(_parentFrame,
                        if (JOptionPane.showOptionDialog(_parentFrame,
-                                       I18nManager.getText("dialog.exportpov.warningtracksize"),
+                                       I18nManager.getText("dialog.3d.warningtracksize"),
                                        I18nManager.getText("function.show3d"), JOptionPane.OK_CANCEL_OPTION,
                                        JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
                                == JOptionPane.OK_OPTION)
                                        I18nManager.getText("function.show3d"), JOptionPane.OK_CANCEL_OPTION,
                                        JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
                                == JOptionPane.OK_OPTION)
@@ -191,18 +191,7 @@ public class Java3DWindow implements ThreeDWindow
                                }
                        }});
                panel.add(svgButton);
                                }
                        }});
                panel.add(svgButton);
-               // Display coordinates of lat/long lines of 3d graph in separate dialog
-               JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
-               showLinesButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               double[] latLines = _model.getLatitudeLines();
-                               double[] lonLines = _model.getLongitudeLines();
-                               LineDialog dialog = new LineDialog(_frame, latLines, lonLines);
-                               dialog.showDialog();
-                       }
-               });
-               panel.add(showLinesButton);
+
                // Close button
                JButton closeButton = new JButton(I18nManager.getText("button.close"));
                closeButton.addActionListener(new ActionListener()
                // Close button
                JButton closeButton = new JButton(I18nManager.getText("button.close"));
                closeButton.addActionListener(new ActionListener()
@@ -305,9 +294,6 @@ public class Java3DWindow implements ThreeDWindow
                _model.setAltitudeFactor(_altFactor);
                _model.scale();
 
                _model.setAltitudeFactor(_altFactor);
                _model.scale();
 
-               // Lat/Long lines
-               objTrans.addChild(createLatLongs(_model));
-
                // Add points to model
                objTrans.addChild(createDataPoints(_model));
 
                // Add points to model
                objTrans.addChild(createDataPoints(_model));
 
@@ -360,70 +346,6 @@ public class Java3DWindow implements ThreeDWindow
        }
 
 
        }
 
 
-       /**
-        * Create all the latitude and longitude lines on the base plane
-        * @param inModel model containing data
-        * @return Group object containing cylinders for lat and long lines
-        */
-       private static Group createLatLongs(ThreeDModel inModel)
-       {
-               Group group = new Group();
-               int numlines = inModel.getLatitudeLines().length;
-               for (int i=0; i<numlines; i++)
-               {
-                       group.addChild(createLatLine(inModel.getScaledLatitudeLine(i), inModel.getModelSize()));
-               }
-               numlines = inModel.getLongitudeLines().length;
-               for (int i=0; i<numlines; i++)
-               {
-                       group.addChild(createLonLine(inModel.getScaledLongitudeLine(i), inModel.getModelSize()));
-               }
-               return group;
-       }
-
-
-       /**
-        * Make a single latitude line for the specified latitude
-        * @param inLatitude latitude in scaled units
-        * @param inSize size of model, for length of line
-        * @return Group object containing cylinder for latitude line
-        */
-       private static Group createLatLine(double inLatitude, double inSize)
-       {
-               Cylinder latline = new Cylinder(0.1f, (float) (inSize*2));
-               Transform3D horizShift = new Transform3D();
-               horizShift.setTranslation(new Vector3d(0.0, 0.0, -inLatitude));
-               TransformGroup horizTrans = new TransformGroup(horizShift);
-               Transform3D zRot = new Transform3D();
-               zRot.rotZ(Math.toRadians(90.0));
-               TransformGroup zTrans = new TransformGroup(zRot);
-               horizTrans.addChild(zTrans);
-               zTrans.addChild(latline);
-               return horizTrans;
-       }
-
-
-       /**
-        * Make a single longitude line for the specified longitude
-        * @param inLongitude longitude in scaled units
-        * @param inSize size of model, for length of line
-        * @return Group object containing cylinder for longitude line
-        */
-       private static Group createLonLine(double inLongitude, double inSize)
-       {
-               Cylinder lonline = new Cylinder(0.1f, (float) (inSize*2));
-               Transform3D horizShift = new Transform3D();
-               horizShift.setTranslation(new Vector3d(inLongitude, 0.0, 0.0));
-               TransformGroup horizTrans = new TransformGroup(horizShift);
-               Transform3D xRot = new Transform3D();
-               xRot.rotX(Math.toRadians(90.0));
-               TransformGroup xTrans = new TransformGroup(xRot);
-               horizTrans.addChild(xTrans);
-               xTrans.addChild(lonline);
-               return horizTrans;
-       }
-
-
        /**
         * Make a Group of the data points to be added
         * @param inModel model containing data
        /**
         * Make a Group of the data points to be added
         * @param inModel model containing data
@@ -442,15 +364,18 @@ public class Java3DWindow implements ThreeDWindow
                                // Add waypoint
                                // Note that x, y and z are horiz, altitude, -vert
                                group.addChild(createWaypoint(new Point3d(
                                // Add waypoint
                                // Note that x, y and z are horiz, altitude, -vert
                                group.addChild(createWaypoint(new Point3d(
-                                       inModel.getScaledHorizValue(i), inModel.getScaledAltValue(i), -inModel.getScaledVertValue(i))));
+                                       inModel.getScaledHorizValue(i) * MODEL_SCALE_FACTOR,
+                                       inModel.getScaledAltValue(i)   * MODEL_SCALE_FACTOR,
+                                       -inModel.getScaledVertValue(i) * MODEL_SCALE_FACTOR)));
                        }
                        else
                        {
                                // Add colour-coded track point
                                // Note that x, y and z are horiz, altitude, -vert
                                group.addChild(createTrackpoint(new Point3d(
                        }
                        else
                        {
                                // Add colour-coded track point
                                // Note that x, y and z are horiz, altitude, -vert
                                group.addChild(createTrackpoint(new Point3d(
-                                       inModel.getScaledHorizValue(i), inModel.getScaledAltValue(i), -inModel.getScaledVertValue(i)),
-                                       inModel.getPointHeightCode(i)));
+                                       inModel.getScaledHorizValue(i) * MODEL_SCALE_FACTOR,
+                                       inModel.getScaledAltValue(i)   * MODEL_SCALE_FACTOR,
+                                       -inModel.getScaledVertValue(i) * MODEL_SCALE_FACTOR), inModel.getPointHeightCode(i)));
                        }
                }
                return group;
                        }
                }
                return group;
diff --git a/tim/prune/threedee/LineDialog.java b/tim/prune/threedee/LineDialog.java
deleted file mode 100644 (file)
index e371bd0..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-package tim.prune.threedee;
-
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JEditorPane;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-
-import tim.prune.I18nManager;
-import tim.prune.data.Latitude;
-import tim.prune.data.Longitude;
-
-/**
- * Class to show a dialog displaying the line coordinates
- * for a 3d view (either java3d or povray)
- */
-public class LineDialog
-{
-       private JDialog _dialog = null;
-       private JFrame _parent = null;
-       private double[] _latLines = null;
-       private double[] _lonLines = null;
-
-
-       /**
-        * Constructor giving parent frame, latitude and longitude lines
-        * @param inParent parent frame for dialog
-        * @param inLatLines latitude lines as doubles
-        * @param inLonLines longitude lines as doubles
-        */
-       public LineDialog(JFrame inParent, double[] inLatLines, double[] inLonLines)
-       {
-               _parent = inParent;
-               _latLines = inLatLines;
-               _lonLines = inLonLines;
-       }
-
-
-       /**
-        * Show the dialog with the lines
-        */
-       public void showDialog()
-       {
-               _dialog = new JDialog(_parent, I18nManager.getText("dialog.3dlines.title"), true);
-               _dialog.setLocationRelativeTo(_parent);
-               _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-               _dialog.getContentPane().add(makeDialogComponents());
-               _dialog.pack();
-               _dialog.setVisible(true);
-       }
-
-
-       /**
-        * @return dialog components
-        */
-       private JPanel makeDialogComponents()
-       {
-               JPanel panel = new JPanel();
-               panel.setLayout(new BorderLayout());
-               StringBuffer descBuffer = new StringBuffer();
-               final int numLatLines = (_latLines == null?0:_latLines.length);
-               final int numLonLines = (_lonLines == null?0:_lonLines.length);
-               if (numLatLines == 0 && numLonLines == 0)
-               {
-                       descBuffer.append("<p>").append(I18nManager.getText("dialog.3dlines.empty")).append("</p>");
-               }
-               else
-               {
-                       descBuffer.append("<p>").append(I18nManager.getText("dialog.3dlines.intro")).append(":</p>");
-                       descBuffer.append("<p>").append(I18nManager.getText("fieldname.latitude")).append("<ul>");
-                       Latitude lat = null;
-                       for (int i=0; i<numLatLines; i++)
-                       {
-                               lat = new Latitude(_latLines[i], Latitude.FORMAT_DEG);
-                               descBuffer.append("<li>").append(lat.output(Latitude.FORMAT_DEG_WHOLE_MIN)).append("</li>");
-                       }
-                       descBuffer.append("</ul></p>");
-                       descBuffer.append("<p>").append(I18nManager.getText("fieldname.longitude")).append("<ul>");
-                       Longitude lon = null;
-                       for (int i=0; i<numLonLines; i++)
-                       {
-                               lon = new Longitude(_lonLines[i], Longitude.FORMAT_DEG);
-                               descBuffer.append("<li>").append(lon.output(Longitude.FORMAT_DEG_WHOLE_MIN)).append("</li>");
-                       }
-                       descBuffer.append("</ul></p>");
-               }
-               JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
-               descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
-               descPane.setEditable(false);
-               descPane.setOpaque(false);
-               panel.add(descPane, BorderLayout.CENTER);
-               // ok button
-               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();
-                               _dialog = null;
-                       }
-               });
-               buttonPanel.add(okButton);
-               panel.add(buttonPanel, BorderLayout.SOUTH);
-               return panel;
-       }
-}
index 2c9ac97784e79f24e2c378766283720e354c8a15..ddf91623b717309daf1bb3bfcd82dadefe850097 100644 (file)
@@ -1,6 +1,5 @@
 package tim.prune.threedee;
 
 package tim.prune.threedee;
 
-import tim.prune.data.Altitude;
 import tim.prune.data.DataPoint;
 import tim.prune.data.PointScaler;
 import tim.prune.data.Track;
 import tim.prune.data.DataPoint;
 import tim.prune.data.PointScaler;
 import tim.prune.data.Track;
@@ -16,12 +15,11 @@ public class ThreeDModel
        private PointScaler _scaler = null;
        private double _scaleFactor = 1.0;
        private double _altFactor = 1.0;
        private PointScaler _scaler = null;
        private double _scaleFactor = 1.0;
        private double _altFactor = 1.0;
+       private double _externalScaleFactor = 1.0;
        // MAYBE: How to store rods (lifts) in data?
        private byte[] _pointTypes = null;
        private byte[] _pointHeights = null;
 
        // MAYBE: How to store rods (lifts) in data?
        private byte[] _pointTypes = null;
        private byte[] _pointHeights = null;
 
-       private static final double MODEL_SIZE = 10.0;
-
        // Constants for point types
        public static final byte POINT_TYPE_WAYPOINT      = 1;
        public static final byte POINT_TYPE_NORMAL_POINT  = 2;
        // Constants for point types
        public static final byte POINT_TYPE_WAYPOINT      = 1;
        public static final byte POINT_TYPE_NORMAL_POINT  = 2;
@@ -57,6 +55,14 @@ public class ThreeDModel
                }
        }
 
                }
        }
 
+       /**
+        * @param inSize size of model
+        */
+       public void setModelSize(double inSize)
+       {
+               _externalScaleFactor = inSize;
+       }
+
        /**
         * Scale all points and calculate factors
         */
        /**
         * Scale all points and calculate factors
         */
@@ -64,30 +70,16 @@ public class ThreeDModel
        {
                // Use PointScaler to sort out x and y values
                _scaler = new PointScaler(_track);
        {
                // Use PointScaler to sort out x and y values
                _scaler = new PointScaler(_track);
-               _scaler.scale();
-               // Calculate scale factor to fit within box
-               _scaleFactor = 1.0;
-               if (_scaler.getMaximumHoriz() > 0.0 || _scaler.getMaximumVert() > 0.0)
-               {
-                       if (_scaler.getMaximumHoriz() > _scaler.getMaximumVert())
-                       {
-                               // scale limited by longitude
-                               _scaleFactor = MODEL_SIZE / _scaler.getMaximumHoriz();
-                       }
-                       else
-                       {
-                               // scale limited by latitude
-                               _scaleFactor = MODEL_SIZE / _scaler.getMaximumVert();
-                       }
-               }
+               _scaler.scale(); // Add 10% border
+
                // cap altitude scale factor if it's too big
                // cap altitude scale factor if it's too big
-               double maxScaledAlt = _scaler.getMaxScaledAlt() * _altFactor;
-               if (maxScaledAlt > MODEL_SIZE) {
+               double maxAlt = _scaler.getAltitudeRange() * _altFactor;
+               if (maxAlt > 0.5)
+               {
                        // capped
                        // capped
-                       _altFactor = _altFactor * MODEL_SIZE / maxScaledAlt;
+                       //System.out.println("Capped alt factor from " + _altFactor + " to " + (_altFactor * 0.5 / maxAlt));
+                       _altFactor = _altFactor * 0.5 / maxAlt;
                }
                }
-               // calculate lat/long lines
-               _scaler.calculateLatLongLines();
 
                // calculate point types and height codes
                calculatePointTypes();
 
                // calculate point types and height codes
                calculatePointTypes();
@@ -108,7 +100,7 @@ public class ThreeDModel
                        DataPoint point = _track.getPoint(i);
                        _pointTypes[i] = (point.isWaypoint()?POINT_TYPE_WAYPOINT:
                                (point.getSegmentStart()?POINT_TYPE_SEGMENT_START:POINT_TYPE_NORMAL_POINT));
                        DataPoint point = _track.getPoint(i);
                        _pointTypes[i] = (point.isWaypoint()?POINT_TYPE_WAYPOINT:
                                (point.getSegmentStart()?POINT_TYPE_SEGMENT_START:POINT_TYPE_NORMAL_POINT));
-                       _pointHeights[i] = (byte) (point.getAltitude().getValue(Altitude.Format.METRES) / 500);
+                       _pointHeights[i] = (byte) (point.getAltitude().getMetricValue() / 500);
                }
        }
 
                }
        }
 
@@ -120,7 +112,7 @@ public class ThreeDModel
         */
        public double getScaledHorizValue(int inIndex)
        {
         */
        public double getScaledHorizValue(int inIndex)
        {
-               return _scaler.getHorizValue(inIndex) * _scaleFactor;
+               return _scaler.getHorizValue(inIndex) * _scaleFactor * _externalScaleFactor;
        }
 
        /**
        }
 
        /**
@@ -130,7 +122,7 @@ public class ThreeDModel
         */
        public double getScaledVertValue(int inIndex)
        {
         */
        public double getScaledVertValue(int inIndex)
        {
-               return _scaler.getVertValue(inIndex) * _scaleFactor;
+               return _scaler.getVertValue(inIndex) * _scaleFactor * _externalScaleFactor;
        }
 
        /**
        }
 
        /**
@@ -144,44 +136,10 @@ public class ThreeDModel
                double altVal = _scaler.getAltValue(inIndex);
                if (altVal < 0) return 0;
                // scale according to exaggeration factor
                double altVal = _scaler.getAltValue(inIndex);
                if (altVal < 0) return 0;
                // scale according to exaggeration factor
-               return altVal * _altFactor;
+               return altVal * _altFactor * _externalScaleFactor;
        }
 
 
        }
 
 
-       /**
-        * @return latitude lines
-        */
-       public double[] getLatitudeLines()
-       {
-               return _scaler.getLatitudeLines();
-       }
-
-       /**
-        * @param inIndex index of line, starting at 0
-        * @return scaled position of latitude line
-        */
-       public double getScaledLatitudeLine(int inIndex)
-       {
-               return _scaler.getScaledLatitudeLines()[inIndex] * _scaleFactor;
-       }
-
-       /**
-        * @return longitude lines
-        */
-       public double[] getLongitudeLines()
-       {
-               return _scaler.getLongitudeLines();
-       }
-
-       /**
-        * @param inIndex index of line, starting at 0
-        * @return scaled position of longitude line
-        */
-       public double getScaledLongitudeLine(int inIndex)
-       {
-               return _scaler.getScaledLongitudeLines()[inIndex] * _scaleFactor;
-       }
-
        /**
         * @param inIndex index of point, starting at 0
         * @return point type, either POINT_TYPE_WAYPOINT or POINT_TYPE_NORMAL_POINT
        /**
         * @param inIndex index of point, starting at 0
         * @return point type, either POINT_TYPE_WAYPOINT or POINT_TYPE_NORMAL_POINT
@@ -199,12 +157,4 @@ public class ThreeDModel
        {
                return _pointHeights[inIndex];
        }
        {
                return _pointHeights[inIndex];
        }
-
-       /**
-        * @return the current model size
-        */
-       public double getModelSize()
-       {
-               return MODEL_SIZE;
-       }
 }
 }
index ba2b31ab5ef89540b8496be4e8908f0a9855ae7e..58f09e9c267afd867c104fa316fc058878367b89 100644 (file)
@@ -14,7 +14,7 @@ public class UndoDeleteRange implements UndoOperation
        /**\r
         * Inner class to hold a single range information set\r
         */\r
        /**\r
         * Inner class to hold a single range information set\r
         */\r
-       class RangeInfo\r
+       static class RangeInfo\r
        {\r
                public int _startIndex = -1;\r
                public DataPoint[] _points = null;\r
        {\r
                public int _startIndex = -1;\r
                public DataPoint[] _points = null;\r