]> gitweb.fperrin.net Git - GpsPrune.git/commitdiff
Version 19, May 2018
authoractivityworkshop <mail@activityworkshop.net>
Fri, 18 May 2018 14:17:37 +0000 (16:17 +0200)
committeractivityworkshop <mail@activityworkshop.net>
Fri, 18 May 2018 14:17:37 +0000 (16:17 +0200)
138 files changed:
README.md
tim/prune/App.java
tim/prune/FunctionLibrary.java
tim/prune/GpsPrune.java
tim/prune/config/Config.java
tim/prune/config/TimezoneHelper.java [new file with mode: 0644]
tim/prune/copyright.txt
tim/prune/correlate/AudioCorrelator.java
tim/prune/correlate/Correlator.java
tim/prune/correlate/MediaPreviewTableModel.java
tim/prune/correlate/MediaSelectionTableModel.java
tim/prune/data/AltitudeRange.java
tim/prune/data/AudioClip.java
tim/prune/data/DataPoint.java
tim/prune/data/GradientCalculator.java
tim/prune/data/IntegerRange.java
tim/prune/data/SpeedCalculator.java
tim/prune/data/Timestamp.java
tim/prune/data/TimestampLocal.java [new file with mode: 0644]
tim/prune/data/TimestampUtc.java [new file with mode: 0644]
tim/prune/data/Track.java
tim/prune/function/AboutScreen.java
tim/prune/function/AddTimeOffset.java
tim/prune/function/CheckVersionScreen.java
tim/prune/function/ConvertNamesToTimes.java
tim/prune/function/CreateMarkerWaypointsFunction.java [new file with mode: 0644]
tim/prune/function/DistanceTimeLimitFunction.java [new file with mode: 0644]
tim/prune/function/DownloadOsmFunction.java
tim/prune/function/Export3dFunction.java
tim/prune/function/HelpScreen.java
tim/prune/function/SearchOsmPoisFunction.java [new file with mode: 0644]
tim/prune/function/SearchOsmPoisXmlHandler.java [new file with mode: 0644]
tim/prune/function/SelectTimezoneFunction.java [new file with mode: 0644]
tim/prune/function/SetLineWidth.java [deleted file]
tim/prune/function/browser/UrlGenerator.java
tim/prune/function/deletebydate/DateInfo.java
tim/prune/function/deletebydate/DateInfoList.java
tim/prune/function/deletebydate/DeleteByDateFunction.java
tim/prune/function/deletebydate/DeletionTableModel.java
tim/prune/function/estimate/jama/QRDecomposition.java
tim/prune/function/gpsies/GpsiesXmlHandler.java
tim/prune/function/gpsies/TrackListModel.java
tim/prune/function/search/GenericDownloaderFunction.java
tim/prune/function/search/SearchResult.java
tim/prune/function/search/wikimedia_galleries.txt
tim/prune/function/settings/AddMapSourceDialog.java [moved from tim/prune/function/AddMapSourceDialog.java with 99% similarity]
tim/prune/function/settings/MapSourceListModel.java [moved from tim/prune/function/MapSourceListModel.java with 96% similarity]
tim/prune/function/settings/SaveConfig.java [moved from tim/prune/function/SaveConfig.java with 99% similarity]
tim/prune/function/settings/SetAltitudeTolerance.java [moved from tim/prune/function/SetAltitudeTolerance.java with 95% similarity]
tim/prune/function/settings/SetColours.java [moved from tim/prune/function/SetColours.java with 99% similarity]
tim/prune/function/settings/SetDisplaySettings.java [new file with mode: 0644]
tim/prune/function/settings/SetLanguage.java [moved from tim/prune/function/SetLanguage.java with 97% similarity]
tim/prune/function/settings/SetMapBgFunction.java [moved from tim/prune/function/SetMapBgFunction.java with 99% similarity]
tim/prune/function/settings/SetPathsFunction.java [moved from tim/prune/function/SetPathsFunction.java with 99% similarity]
tim/prune/function/sew/SplitSegmentsFunction.java
tim/prune/gui/CombinedListAndModel.java [new file with mode: 0644]
tim/prune/gui/DetailsDisplay.java
tim/prune/gui/IconManager.java
tim/prune/gui/ImageUtils.java
tim/prune/gui/MenuManager.java
tim/prune/gui/MultiStateCheckBox.java [moved from tim/prune/gui/TripleStateCheckBox.java with 62% similarity]
tim/prune/gui/PhotoThumbnail.java
tim/prune/gui/colour/ColourerCaretaker.java
tim/prune/gui/colour/DateColourer.java
tim/prune/gui/images/points_arrows.png [new file with mode: 0644]
tim/prune/gui/images/points_connected.png
tim/prune/gui/images/points_disconnected.png
tim/prune/gui/images/points_hidden.png
tim/prune/gui/images/wpicon_default_m.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_pin_l.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_pin_m.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_pin_s.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_plectrum_l.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_plectrum_m.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_plectrum_s.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ring_l.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ring_m.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ring_s.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ringpt_l.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ringpt_m.png [new file with mode: 0644]
tim/prune/gui/images/wpicon_ringpt_s.png [new file with mode: 0644]
tim/prune/gui/map/MapCanvas.java
tim/prune/gui/map/MapSourceLibrary.java
tim/prune/gui/map/OsmMapSource.java
tim/prune/gui/map/WpIconDefinition.java [new file with mode: 0644]
tim/prune/gui/map/WpIconLibrary.java [new file with mode: 0644]
tim/prune/jpeg/ExifGateway.java [deleted file]
tim/prune/jpeg/ExifLibrary.java [deleted file]
tim/prune/jpeg/ExifLibrarySwitch.java [deleted file]
tim/prune/jpeg/ExternalExifLibrary.java [deleted file]
tim/prune/jpeg/InternalExifLibrary.java
tim/prune/jpeg/drew/ByteArrayReader.java [new file with mode: 0644]
tim/prune/jpeg/drew/ExifException.java [new file with mode: 0644]
tim/prune/jpeg/drew/ExifReader.java
tim/prune/jpeg/drew/ExifTiffHandler.java [new file with mode: 0644]
tim/prune/jpeg/drew/JpegException.java [deleted file]
tim/prune/jpeg/drew/JpegSegmentData.java [deleted file]
tim/prune/jpeg/drew/JpegSegmentReader.java [deleted file]
tim/prune/jpeg/drew/Rational.java
tim/prune/jpeg/drew/TiffDataFormat.java [new file with mode: 0644]
tim/prune/jpeg/drew/TiffProcessor.java [new file with mode: 0644]
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_es.properties
tim/prune/lang/prune-texts_fi.properties [new file with mode: 0644]
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_no.properties [new file with mode: 0644]
tim/prune/lang/prune-texts_pl.properties
tim/prune/lang/prune-texts_pt.properties
tim/prune/lang/prune-texts_ro.properties
tim/prune/lang/prune-texts_ru.properties
tim/prune/lang/prune-texts_sv.properties
tim/prune/lang/prune-texts_uk.properties
tim/prune/lang/prune-texts_zh.properties
tim/prune/load/FieldGuesser.java
tim/prune/load/FileLoader.java
tim/prune/load/JpegLoader.java
tim/prune/load/TextFileLoader.java
tim/prune/readme.txt
tim/prune/save/FileSaver.java
tim/prune/save/GpxExporter.java
tim/prune/save/ImageExporter.java
tim/prune/save/KmlExporter.java
tim/prune/save/PovExporter.java
tim/prune/save/SvgExporter.java [deleted file]
tim/prune/save/SvgFragment.java [deleted file]
tim/prune/threedee/Java3DWindow.java
tim/prune/undo/UndoAddTimeOffset.java
tim/prune/undo/UndoAppendPoints.java [new file with mode: 0644]

index 27af085cdc66e652413bc2307cfd655a01cde1f7..5820cd0bec06a70349b837b9f4742c5f90133eba 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,5 +3,5 @@ GpsPrune is a map-based application for viewing, editing and converting coordina
 
 It's a cross-platform java application, and its home page is at https://gpsprune.activityworkshop.net .
 
-Here on github you'll find all the sources from version 1 to the current version 18.6, and in the wiki at https://github.com/activityworkshop/GpsPrune/wiki there's the beginning of a translation effort for anyone to contribute.
+Here on github you'll find all the sources from version 1 to the current version 19, and in the wiki at https://github.com/activityworkshop/GpsPrune/wiki there's the beginning of a translation effort for anyone to contribute.
 Currently just the Spanish translations are online, to see whether it's a workable idea or not.  Please help with this if you can.
index 81e59cc69b23566e040406ffe6f93f0023e9ea6d..3a778588ab815a7f30c4b396e4de9d2c266836da 100644 (file)
@@ -24,10 +24,10 @@ 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.edit.FieldEditList;
 import tim.prune.function.edit.PointEditor;
+import tim.prune.function.settings.SaveConfig;
 import tim.prune.gui.MenuManager;
 import tim.prune.gui.SidebarController;
 import tim.prune.gui.UndoManager;
@@ -402,16 +402,16 @@ public class App
        }
 
        /**
-        * Complete the add time offset function with the specified offset
+        * Complete the add time offset function with the specified offset in seconds
         * @param inTimeOffset time offset to add (+ve for add, -ve for subtract)
         */
-       public void finishAddTimeOffset(long inTimeOffset)
+       public void finishAddTimeOffsetSeconds(long inTimeOffset)
        {
                // Construct undo information
                int selStart = _trackInfo.getSelection().getStart();
                int selEnd = _trackInfo.getSelection().getEnd();
                UndoAddTimeOffset undo = new UndoAddTimeOffset(selStart, selEnd, inTimeOffset);
-               if (_trackInfo.getTrack().addTimeOffset(selStart, selEnd, inTimeOffset, false))
+               if (_trackInfo.getTrack().addTimeOffsetSeconds(selStart, selEnd, inTimeOffset, false))
                {
                        _undoStack.add(undo);
                        UpdateMessageBroker.informSubscribers(DataSubscriber.DATA_EDITED);
index 81e5e0f2d20a3518bdce100899d4bc4d46cfe4a9..0441826164d930de8b79acb7d45fd84a742d33ce 100644 (file)
@@ -2,7 +2,43 @@ package tim.prune;
 
 import tim.prune.correlate.AudioCorrelator;
 import tim.prune.correlate.PhotoCorrelator;
-import tim.prune.function.*;
+import tim.prune.function.AboutScreen;
+import tim.prune.function.AddAltitudeOffset;
+import tim.prune.function.AddTimeOffset;
+import tim.prune.function.CheckVersionScreen;
+import tim.prune.function.ConnectToPointFunction;
+import tim.prune.function.ConvertNamesToTimes;
+import tim.prune.function.CreateMarkerWaypointsFunction;
+import tim.prune.function.CropToSelection;
+import tim.prune.function.DeleteFieldValues;
+import tim.prune.function.DeleteSelectedRangeFunction;
+import tim.prune.function.DisconnectAudioFunction;
+import tim.prune.function.DisconnectPhotoFunction;
+import tim.prune.function.DiskCacheConfig;
+import tim.prune.function.DownloadOsmFunction;
+import tim.prune.function.DuplicatePoint;
+import tim.prune.function.FindWaypoint;
+import tim.prune.function.FullRangeDetails;
+import tim.prune.function.GetWikipediaFunction;
+import tim.prune.function.HelpScreen;
+import tim.prune.function.IgnoreExifThumb;
+import tim.prune.function.InterpolateFunction;
+import tim.prune.function.PasteCoordinates;
+import tim.prune.function.PhotoPopupFunction;
+import tim.prune.function.PlayAudioFunction;
+import tim.prune.function.RearrangePhotosFunction;
+import tim.prune.function.RearrangeWaypointsFunction;
+import tim.prune.function.RemoveAudioFunction;
+import tim.prune.function.RemovePhotoFunction;
+import tim.prune.function.RotatePhoto;
+import tim.prune.function.SearchOsmPoisFunction;
+import tim.prune.function.SearchWikipediaNames;
+import tim.prune.function.SelectSegmentFunction;
+import tim.prune.function.SelectTimezoneFunction;
+import tim.prune.function.ShowKeysScreen;
+import tim.prune.function.ShowThreeDFunction;
+import tim.prune.function.SingleNumericParameterFunction;
+import tim.prune.function.StopAudioFunction;
 import tim.prune.function.autoplay.AutoplayFunction;
 import tim.prune.function.charts.Charter;
 import tim.prune.function.compress.CompressTrackFunction;
@@ -16,6 +52,13 @@ 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.settings.SaveConfig;
+import tim.prune.function.settings.SetAltitudeTolerance;
+import tim.prune.function.settings.SetColours;
+import tim.prune.function.settings.SetDisplaySettings;
+import tim.prune.function.settings.SetLanguage;
+import tim.prune.function.settings.SetMapBgFunction;
+import tim.prune.function.settings.SetPathsFunction;
 import tim.prune.function.sew.SewTrackSegmentsFunction;
 import tim.prune.function.sew.SplitSegmentsFunction;
 import tim.prune.function.srtm.DownloadSrtmFunction;
@@ -29,7 +72,7 @@ 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;
+
 
 /**
  * Class to provide access to functions
@@ -39,7 +82,6 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_GPXEXPORT = 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;
@@ -50,6 +92,7 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_SELECT_SEGMENT = null;
        public static GenericFunction FUNCTION_SPLIT_SEGMENTS = null;
        public static GenericFunction FUNCTION_SEW_SEGMENTS = null;
+       public static GenericFunction FUNCTION_CREATE_MARKER_WAYPOINTS = null;
        public static GenericFunction FUNCTION_REARRANGE_PHOTOS = null;
        public static GenericFunction FUNCTION_COMPRESS = null;
        public static GenericFunction FUNCTION_MARK_LIFTS = null;
@@ -63,6 +106,7 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_DOWNLOAD_SRTM = null;
        public static GenericFunction FUNCTION_NEARBY_WIKIPEDIA = null;
        public static GenericFunction FUNCTION_SEARCH_WIKIPEDIA = null;
+       public static GenericFunction FUNCTION_SEARCH_OSMPOIS = null;
        public static GenericFunction FUNCTION_DOWNLOAD_OSM = null;
        public static GenericFunction FUNCTION_ADD_TIME_OFFSET  = null;
        public static GenericFunction FUNCTION_ADD_ALTITUDE_OFFSET  = null;
@@ -95,13 +139,14 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_CORRELATE_AUDIOS = null;
        public static GenericFunction FUNCTION_PLAY_AUDIO = null;
        public static GenericFunction FUNCTION_STOP_AUDIO = null;
+       public static GenericFunction FUNCTION_SET_DISPLAY_SETTINGS = 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_COLOURS = null;
-       public static SingleNumericParameterFunction FUNCTION_SET_LINE_WIDTH = null;
        public static GenericFunction FUNCTION_SET_LANGUAGE = null;
        public static SingleNumericParameterFunction FUNCTION_SET_ALTITUDE_TOLERANCE = null;
+       public static GenericFunction FUNCTION_SET_TIMEZONE = null;
        public static GenericFunction FUNCTION_HELP   = null;
        public static GenericFunction FUNCTION_SHOW_KEYS = null;
        public static GenericFunction FUNCTION_ABOUT  = null;
@@ -117,7 +162,6 @@ public abstract class FunctionLibrary
                FUNCTION_GPXEXPORT = new GpxExporter(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);
@@ -128,6 +172,7 @@ public abstract class FunctionLibrary
                FUNCTION_SELECT_SEGMENT = new SelectSegmentFunction(inApp);
                FUNCTION_SPLIT_SEGMENTS = new SplitSegmentsFunction(inApp);
                FUNCTION_SEW_SEGMENTS = new SewTrackSegmentsFunction(inApp);
+               FUNCTION_CREATE_MARKER_WAYPOINTS = new CreateMarkerWaypointsFunction(inApp);
                FUNCTION_REARRANGE_PHOTOS = new RearrangePhotosFunction(inApp);
                FUNCTION_COMPRESS = new CompressTrackFunction(inApp);
                FUNCTION_MARK_LIFTS = new MarkLiftsFunction(inApp);
@@ -141,6 +186,7 @@ public abstract class FunctionLibrary
                FUNCTION_DOWNLOAD_SRTM = new DownloadSrtmFunction(inApp);
                FUNCTION_NEARBY_WIKIPEDIA = new GetWikipediaFunction(inApp);
                FUNCTION_SEARCH_WIKIPEDIA = new SearchWikipediaNames(inApp);
+               FUNCTION_SEARCH_OSMPOIS = new SearchOsmPoisFunction(inApp);
                FUNCTION_DOWNLOAD_OSM = new DownloadOsmFunction(inApp);
                FUNCTION_ADD_TIME_OFFSET = new AddTimeOffset(inApp);
                FUNCTION_ADD_ALTITUDE_OFFSET = new AddAltitudeOffset(inApp);
@@ -173,13 +219,14 @@ public abstract class FunctionLibrary
                FUNCTION_PLAY_AUDIO = new PlayAudioFunction(inApp);
                FUNCTION_STOP_AUDIO = new StopAudioFunction(inApp);
                FUNCTION_DISCONNECT_AUDIO = new DisconnectAudioFunction(inApp);
+               FUNCTION_SET_DISPLAY_SETTINGS = new SetDisplaySettings(inApp);
                FUNCTION_SET_MAP_BG = new SetMapBgFunction(inApp);
                FUNCTION_SET_DISK_CACHE = new DiskCacheConfig(inApp);
                FUNCTION_SET_PATHS = new SetPathsFunction(inApp);
                FUNCTION_SET_COLOURS = new SetColours(inApp);
-               FUNCTION_SET_LINE_WIDTH = new SetLineWidth(inApp);
                FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
                FUNCTION_SET_ALTITUDE_TOLERANCE = new SetAltitudeTolerance(inApp);
+               FUNCTION_SET_TIMEZONE = new SelectTimezoneFunction(inApp);
                FUNCTION_HELP   = new HelpScreen(inApp);
                FUNCTION_SHOW_KEYS = new ShowKeysScreen(inApp);
                FUNCTION_ABOUT  = new AboutScreen(inApp);
index 492a457340d69f8274018efa3f95fe1368e51e7a..67b197cde70a2efab900ec6631c2ae73c900a568 100644 (file)
@@ -28,17 +28,17 @@ import tim.prune.gui.profile.ProfileChart;
 
 /**
  * GpsPrune is a tool to visualize, edit, convert and prune GPS data
- * Please see the included readme.txt or http://activityworkshop.net
- * This software is copyright activityworkshop.net 2006-2016 and made available through the Gnu GPL version 2.
+ * Please see the included readme.txt or https://activityworkshop.net
+ * This software is copyright activityworkshop.net 2006-2018 and made available through the Gnu GPL version 2.
  * For license details please see the included license.txt.
  * GpsPrune is the main entry point to the application, including initialisation and launch
  */
 public class GpsPrune
 {
        /** Version number of application, used in about screen and for version check */
-       public static final String VERSION_NUMBER = "18.6";
+       public static final String VERSION_NUMBER = "19";
        /** Build number, just used for about screen */
-       public static final String BUILD_NUMBER = "343";
+       public static final String BUILD_NUMBER = "362";
        /** Static reference to App object */
        private static App APP = null;
 
index d2d49c68d7ebb93485c9a428ab6c2fb3c40f3eec..cd23dbdc724ba5c738666774a1f186c9458b6cc5 100644 (file)
@@ -99,6 +99,12 @@ public abstract class Config
        public static final String KEY_TERRAIN_GRID_SIZE = "prune.terraingridsize";
        /** Key for altitude tolerance */
        public static final String KEY_ALTITUDE_TOLERANCE = "prune.altitudetolerance";
+       /** Key for waypoint icons to use */
+       public static final String KEY_WAYPOINT_ICONS = "prune.waypointicons";
+       /** Size of waypoint icons to use */
+       public static final String KEY_WAYPOINT_ICON_SIZE = "prune.waypointiconsize";
+       /** Id of selected timezone */
+       public static final String KEY_TIMEZONE_ID = "prune.timezoneid";
 
 
        /** Initialise the default properties */
@@ -194,6 +200,7 @@ public abstract class Config
                props.put(KEY_HEIGHT_EXAGGERATION, "100"); // 100%, no exaggeration
                props.put(KEY_TERRAIN_GRID_SIZE, "50");
                props.put(KEY_ALTITUDE_TOLERANCE, "0"); // 0, all exact as before
+               props.put(KEY_WAYPOINT_ICON_SIZE, "1"); // medium size
                return props;
        }
 
diff --git a/tim/prune/config/TimezoneHelper.java b/tim/prune/config/TimezoneHelper.java
new file mode 100644 (file)
index 0000000..f3871d4
--- /dev/null
@@ -0,0 +1,22 @@
+package tim.prune.config;
+
+import java.util.TimeZone;
+
+public abstract class TimezoneHelper
+{
+
+       /**
+        * @return the timezone selected in the Config
+        */
+       public static TimeZone getSelectedTimezone()
+       {
+               final String zoneId = Config.getConfigString(Config.KEY_TIMEZONE_ID);
+               if (zoneId == null || zoneId.equals("")) {
+                       return TimeZone.getDefault();
+               }
+               else {
+                       return TimeZone.getTimeZone(zoneId);
+               }
+       }
+
+}
index e623180111efd8b1c80c45ca8973229a5b5b2baa..3022774654f3f6b66501b143e2ca113bdc103eaa 100644 (file)
@@ -1,9 +1,9 @@
-The source code of GpsPrune is copyright 2006-2016 activityworkshop.net
+The source code of GpsPrune is copyright 2006-2018 activityworkshop.net
 and is distributed under the terms of the Gnu GPL version 2.
 
-Portions of the package jpeg.drew (if included in this package) were taken
-from Drew Noakes' "Metadata extractor" v2.3.1 which is
-copyright Drew Noakes 2002-2006 and was placed in the public domain.
+Portions of the package jpeg.drew were taken
+from Drew Noakes' "Metadata extractor" v2.7.2 which is
+copyright Drew Noakes 2002-2015 and released under Apache 2.0.
 
 Translations are copyright various contributors, some of whom are named
 in the source code and some preferred to remain anonymous.
\ No newline at end of file
index 432674e1547dc7765d8b507fcd2baa9e4205a761..f60d687db38c2164e37f4cea72f62cecaa9d3234 100644 (file)
@@ -11,6 +11,7 @@ import tim.prune.App;
 import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.AudioClip;
 import tim.prune.data.AudioList;
 import tim.prune.data.DataPoint;
@@ -18,6 +19,7 @@ import tim.prune.data.MediaObject;
 import tim.prune.data.MediaList;
 import tim.prune.data.TimeDifference;
 import tim.prune.data.Timestamp;
+import tim.prune.data.TimestampUtc;
 import tim.prune.undo.UndoCorrelateAudios;
 
 /**
@@ -164,14 +166,19 @@ public class AudioCorrelator extends Correlator
        protected Timestamp getMediaTimestamp(MediaObject inMedia)
        {
                Timestamp tstamp = super.getMediaTimestamp(inMedia);
+               long mediaMillis = tstamp.getMilliseconds(TimezoneHelper.getSelectedTimezone());
                try {
                        AudioClip audio = (AudioClip) inMedia;
                        int audioLength = audio.getLengthInSeconds();
                        // Each option is worth half the length of the audio clip, so need to divide by 2
                        int secsToAdd = audioLength *
                                (_correlTimesSelector.getSelectedOption() - _fileTimesSelector.getSelectedOption()) / 2;
-                       if (audioLength > 0 && secsToAdd != 0) {
-                               tstamp = tstamp.createPlusOffset(secsToAdd);
+                       if (audioLength > 0 && secsToAdd != 0)
+                       {
+                               mediaMillis += (secsToAdd * 1000L);
+                               tstamp = new TimestampUtc(mediaMillis);
+                               // Here we create a Utc timestamp but it's only temporary for the correlation
+                               // so it will never have to react to timezone changes
                        }
                }
                catch (ClassCastException cce) {}
index 7e887ffc814868bda09ed32657ef11ea32abc547..0aaafc36577628a1165b5867f539555b2c06abcd 100644 (file)
@@ -6,8 +6,8 @@ import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.util.Calendar;
 import java.util.Iterator;
+import java.util.TimeZone;
 import java.util.TreeSet;
 
 import javax.swing.BorderFactory;
@@ -27,6 +27,7 @@ import javax.swing.JTextField;
 import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Distance;
 import tim.prune.data.Field;
@@ -50,6 +51,7 @@ public abstract class Correlator extends GenericFunction
        protected JTable _previewTable = null;
        private boolean _previewEnabled = false; // flag required to enable preview function on final panel
        private boolean[] _cardEnabled = null; // flag for each card
+       private TimeZone _timezone = null;
        private JTextField _offsetHourBox = null, _offsetMinBox = null, _offsetSecBox = null;
        private JRadioButton _mediaLaterOption = null, _pointLaterOption = null;
        private JRadioButton _timeLimitRadio = null, _distLimitRadio = null;
@@ -59,6 +61,7 @@ public abstract class Correlator extends GenericFunction
        private JButton _nextButton = null, _backButton = null;
        protected JButton _okButton = null;
 
+
        /**
         * Constructor
         * @param inApp App object to report actions to
@@ -110,13 +113,17 @@ public abstract class Correlator extends GenericFunction
                        _dialog.getContentPane().add(makeDialogContents());
                        _dialog.pack();
                }
+               _okButton.setEnabled(false);
+               // Init timezone to the currently selected one
+               _timezone = TimezoneHelper.getSelectedTimezone();
                // Go to first available card
                int card = 0;
                _cardEnabled = null;
-               while (!isCardEnabled(card)) {card++;}
+               while (!isCardEnabled(card)) {
+                       card++;
+               }
                _cards.showCard(card);
                showCard(0); // does set up and next/prev enabling
-               _okButton.setEnabled(false);
                if (!isCardEnabled(1)) {
                        _app.showTip(TipManager.Tip_ManuallyCorrelateOne);
                }
@@ -205,7 +212,7 @@ public abstract class Correlator extends GenericFunction
                                && media.getOriginalStatus() == MediaObject.Status.NOT_CONNECTED)
                        {
                                // Calculate time difference, add to table model
-                               long timeDiff = getMediaTimestamp(media).getSecondsSince(media.getDataPoint().getTimestamp());
+                               long timeDiff = getMediaTimestamp(media).getSecondsSince(media.getDataPoint().getTimestamp(), _timezone);
                                model.addMedia(media, timeDiff);
                        }
                }
@@ -242,27 +249,6 @@ public abstract class Correlator extends GenericFunction
        }
 
 
-       /**
-        * @param inFirstTimestamp timestamp of first photo / audio object, or null if not available
-        * @return time difference of local time zone from UTC when the first photo was taken
-        */
-       private static TimeDifference getTimezoneOffset(Timestamp inFirstTimestamp)
-       {
-               Calendar cal = null;
-               // Use first timestamp if available
-               if (inFirstTimestamp != null) {
-                       cal = inFirstTimestamp.getCalendar();
-               }
-               else {
-                       // No photo or no timestamp, just use current time
-                       cal = Calendar.getInstance();
-               }
-               // Both time zone offset and dst offset are based on milliseconds, so convert to seconds
-               TimeDifference timeDiff = new TimeDifference((cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000);
-               return timeDiff;
-       }
-
-
        /**
         * Calculate the median index to select from the table
         * @param inModel table model
@@ -478,7 +464,9 @@ public abstract class Correlator extends GenericFunction
         */
        private boolean isCardEnabled(int inCardNum)
        {
-               if (_cardEnabled == null) {_cardEnabled = getCardEnabledFlags();}
+               if (_cardEnabled == null) {
+                       _cardEnabled = getCardEnabledFlags();
+               }
                return (inCardNum >= 0 && inCardNum < _cardEnabled.length && _cardEnabled[inCardNum]);
        }
 
@@ -595,7 +583,9 @@ public abstract class Correlator extends GenericFunction
        public void createPreview(boolean inFromButton)
        {
                // Exit if still on first panel
-               if (!_previewEnabled) {return;}
+               if (!_previewEnabled) {
+                       return;
+               }
                // Create a TimeDifference based on the edit boxes
                int numHours = getValue(_offsetHourBox.getText());
                int numMins = getValue(_offsetMinBox.getText());
@@ -615,12 +605,8 @@ public abstract class Correlator extends GenericFunction
                TimeDifference timeDiff = inTimeDiff;
                if (timeDiff == null)
                {
-                       // No time difference available, so calculate based on computer's time zone
-                       Timestamp tstamp = null;
-                       if (inFirstMedia != null) {
-                               tstamp = inFirstMedia.getTimestamp();
-                       }
-                       timeDiff = getTimezoneOffset(tstamp);
+                       // No time difference available, so try with zero
+                       timeDiff = new TimeDifference(0L);
                }
                // Use time difference to set edit boxes
                _offsetHourBox.setText("" + timeDiff.getNumHours());
@@ -664,7 +650,7 @@ public abstract class Correlator extends GenericFunction
                if (inMedia.hasTimestamp())
                {
                        // Add/subtract offset to media timestamp
-                       Timestamp mediaStamp = getMediaTimestamp(inMedia).createMinusOffset(inOffset);
+                       Timestamp mediaStamp = getMediaTimestamp(inMedia);
                        int numPoints = inTrack.getNumPoints();
                        for (int i=0; i<numPoints; i++)
                        {
@@ -674,7 +660,8 @@ public abstract class Correlator extends GenericFunction
                                        Timestamp pointStamp = point.getTimestamp();
                                        if (pointStamp != null && pointStamp.isValid())
                                        {
-                                               long numSeconds = pointStamp.getSecondsSince(mediaStamp);
+                                               long numSeconds = pointStamp.getSecondsSince(mediaStamp, _timezone)
+                                                       + inOffset.getTotalSeconds();
                                                pair.addPoint(point, numSeconds);
                                        }
                                }
index e562ff9f4ff32fc764f70bbec9f6ea09207caa32..26d25f1ca07d359aed0889910b2e4687e910625b 100644 (file)
@@ -1,9 +1,12 @@
 package tim.prune.correlate;
 
 import java.util.ArrayList;
+import java.util.TimeZone;
+
 import javax.swing.table.AbstractTableModel;
 
 import tim.prune.I18nManager;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
 import tim.prune.gui.DisplayUtils;
@@ -19,14 +22,18 @@ public class MediaPreviewTableModel extends AbstractTableModel
        private ArrayList<MediaPreviewTableRow> _list = new ArrayList<MediaPreviewTableRow>();
        /** Distance units */
        private Unit _distanceUnits = UnitSetLibrary.UNITS_KILOMETRES;
+       /** Current timezone */
+       private TimeZone _timezone = null;
 
 
        /**
         * Constructor
         * @param inFirstColumnKey key for first column heading
         */
-       public MediaPreviewTableModel(String inFirstColumnKey) {
+       public MediaPreviewTableModel(String inFirstColumnKey)
+       {
                _firstColumnHeading = I18nManager.getText(inFirstColumnKey);
+               _timezone = TimezoneHelper.getSelectedTimezone();
        }
 
        /**
@@ -83,7 +90,7 @@ public class MediaPreviewTableModel extends AbstractTableModel
                if (inColumnIndex == 0) return row.getMedia().getName();
                else if (inColumnIndex == 1) {
                        if (row.getMedia().hasTimestamp()) {
-                               return row.getMedia().getTimestamp().getText();
+                               return row.getMedia().getTimestamp().getText(_timezone);
                        }
                        return ""; // media doesn't have a timestamp
                }
index 93f8759bfa4c4352d653b489b85bbd649d52eb79..1b90e86b25f3432736bf21a7504856913786e295 100644 (file)
@@ -1,8 +1,11 @@
 package tim.prune.correlate;
 
 import java.util.ArrayList;
+import java.util.TimeZone;
+
 import javax.swing.table.AbstractTableModel;
 import tim.prune.I18nManager;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.MediaObject;
 
 
@@ -18,6 +21,8 @@ public class MediaSelectionTableModel extends AbstractTableModel
        private String _lastColumnHeading = null;
        /** List of rows */
        private ArrayList<MediaSelectionTableRow> _list = new ArrayList<MediaSelectionTableRow>();
+       /** Current timezone */
+       private TimeZone _timezone = null;
 
 
        /**
@@ -29,6 +34,7 @@ public class MediaSelectionTableModel extends AbstractTableModel
        {
                _firstColumnHeading = I18nManager.getText(inFirstColumnKey);
                _lastColumnHeading = I18nManager.getText(inLastColumnKey);
+               _timezone = TimezoneHelper.getSelectedTimezone();
        }
 
        /**
@@ -84,7 +90,8 @@ public class MediaSelectionTableModel extends AbstractTableModel
                MediaSelectionTableRow row = _list.get(inRowIndex);
                if (inColumnIndex == 0) return row.getMedia().getName();
                else if (inColumnIndex == 1) {
-                       return (row.getMedia().hasTimestamp() ? row.getMedia().getTimestamp().getText() : "");
+                       return (row.getMedia().hasTimestamp() ?
+                               row.getMedia().getTimestamp().getText(_timezone) : "");
                }
                else if (inColumnIndex == 2) return row.getTimeDiff().getDescription();
                return (row.getTimeDiff().getIsPositive() ? I18nManager.getText("dialog.about.yes") :
index 17036311cd7558e561a20b4795e916e54547c482..6a9f65fb1d5fdaf087d118e686fd4991fe0f5c75 100644 (file)
@@ -170,27 +170,25 @@ public class AltitudeRange
         */
        public boolean hasRange()
        {
-               return _range.getMaximum() > _range.getMinimum();
+               return _range.hasValues();
        }
 
 
        /**
         * @param inUnit altitude units to use
-        * @return minimum value, or -1 if none found
+        * @return minimum value
         */
        public int getMinimum(Unit inUnit)
        {
-               if (_range.getMinimum() <= 0) return _range.getMinimum();
                return (int) (_range.getMinimum() * inUnit.getMultFactorFromStd());
        }
 
        /**
         * @param inUnit altitude units to use
-        * @return maximum value, or -1 if none found
+        * @return maximum value
         */
        public int getMaximum(Unit inUnit)
        {
-               if (_range.getMaximum() <= 0) return _range.getMaximum();
                return (int) (_range.getMaximum() * inUnit.getMultFactorFromStd());
        }
 
index b6dbd87948d6d60870d30d33b18dcf88a2703a86..2e5f5ee729968c921e360fd9e470bea6ca9277b1 100644 (file)
@@ -23,7 +23,7 @@ public class AudioClip extends MediaObject
        public AudioClip(File inFile)
        {
                // Timestamp is always just taken from the file modification stamp
-               super(inFile, new Timestamp(inFile.lastModified()));
+               super(inFile, new TimestampUtc(inFile.lastModified()));
        }
 
        /**
index c2446228161401202b4d3cbfba107a56f3cfde70..84f4800bcd2c0a0d546da8a83a87868810bf3a54 100644 (file)
@@ -81,7 +81,7 @@ public class DataPoint
                        }
                }
                if (inField == null || inField == Field.TIMESTAMP) {
-                       _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
+                       _timestamp = new TimestampUtc(getFieldValue(Field.TIMESTAMP));
                }
                if (inField == null || inField == Field.WAYPT_NAME) {
                        _waypointName = getFieldValue(Field.WAYPT_NAME);
@@ -118,7 +118,7 @@ public class DataPoint
                        _altitude = inAltitude;
                        _fieldValues[2] = "" + inAltitude.getValue();
                }
-               _timestamp = new Timestamp(null);
+               _timestamp = new TimestampUtc(null);
        }
 
 
@@ -369,12 +369,12 @@ public class DataPoint
         * Add a time offset to this point
         * @param inOffset offset to add (-ve to subtract)
         */
-       public void addTimeOffset(long inOffset)
+       public void addTimeOffsetSeconds(long inOffset)
        {
                if (hasTimestamp())
                {
-                       _timestamp.addOffset(inOffset);
-                       _fieldValues[_fieldList.getFieldIndex(Field.TIMESTAMP)] = _timestamp.getText();
+                       _timestamp.addOffsetSeconds(inOffset);
+                       _fieldValues[_fieldList.getFieldIndex(Field.TIMESTAMP)] = _timestamp.getText(null);
                        setModified(false);
                }
        }
index 97a691fe34178260ca30c8d8e02da1cce208b9b4..0f608bda9c315f359d8ae0f9e5fec75ee1c71f57 100644 (file)
@@ -40,7 +40,7 @@ public abstract class GradientCalculator
                                && p.hasAltitude() && q.hasAltitude())
                        {
                                final double horizRads = DataPoint.calculateRadiansBetween(p, point) +
-                                       DataPoint.calculateRadiansBetween(point,  q);
+                                       DataPoint.calculateRadiansBetween(point, q);
                                final double horizDist = Distance.convertRadiansToDistance(horizRads, UnitSetLibrary.UNITS_METRES);
                                final double heightDiff = q.getAltitude().getMetricValue() - p.getAltitude().getMetricValue();
                                // Get gradient in radians
index a16f44badb2aec77f10061970e8851de47509cba..d2424d1bcf557c272de9d1b850b2b301573c6b22 100644 (file)
@@ -2,11 +2,12 @@ package tim.prune.data;
 
 /**
  * Represents a range of integers, holding the maximum and
- * minimum values.  Values assumed to be >= 0.
+ * minimum values.
  */
 public class IntegerRange
 {
        private int _min = -1, _max = -1;
+       private boolean _foundValues = false;
 
 
        /**
@@ -16,22 +17,31 @@ public class IntegerRange
        {
                _min = -1;
                _max = -1;
+               _foundValues = false;
        }
 
 
        /**
         * Add a value to the range
-        * @param inValue value to add, only positive values considered
+        * @param inValue value to add
         */
        public void addValue(int inValue)
        {
-               if (inValue >= 0)
-               {
-                       if (inValue < _min || _min < 0) _min = inValue;
-                       if (inValue > _max) _max = inValue;
+               if (inValue < _min || !_foundValues) {
+                       _min = inValue;
                }
+               if (inValue > _max || !_foundValues) {
+                       _max = inValue;
+               }
+               _foundValues = true;
        }
 
+       /**
+        * @return true if any values added to the range
+        */
+       public boolean hasValues() {
+               return _foundValues;
+       }
 
        /**
         * @return minimum value, or -1 if none found
index 49fd05665a90e8788e3806e5f740d773f7c4cc0f..96677ca04ae74797fd13080c9978acb439f801d7 100644 (file)
@@ -79,7 +79,7 @@ public abstract class SpeedCalculator
                                        lateStamp = p.getTimestamp();
                                }
 
-                               stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point,  p);
+                               stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point, p);
                                index++;
                                if (p != null && !p.isWaypoint()) {
                                        q = p;
@@ -153,7 +153,7 @@ public abstract class SpeedCalculator
                                                if (p.hasAltitude()) firstAlt = p.getAltitude();
                                        }
 
-                                       stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(p,  point);
+                                       stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(p, point);
                                        index--;
                                }
                                while (!stop);
@@ -173,7 +173,7 @@ public abstract class SpeedCalculator
                                        if (p.hasAltitude()) lastAlt = p.getAltitude();
                                }
 
-                               stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point,  p);
+                               stop = (p == null) || p.getSegmentStart() || hasSufficientTimeDifference(point, p);
                                index++;
                        }
                        while (!stop);
index d5e065d4371d197c177a09f514498f93f71b6109..ac144bb8c59e8247deb67438a9f5c8504c258ca9 100644 (file)
@@ -1,44 +1,29 @@
 package tim.prune.data;
 
+
 import java.text.DateFormat;
-import java.text.ParsePosition;
 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;
+
 
 /**
- * Class to hold the timestamp of a track point
- * and provide conversion functions
+ * Superclass of all timestamp implementations
  */
-public class Timestamp
+public abstract class Timestamp
 {
-       private boolean _valid = false;
-       private long _milliseconds = 0L;
-       private String _text = null;
-
-       private static final DateFormat DEFAULT_DATETIME_FORMAT = DateFormat.getDateTimeInstance();
        private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
        private static final DateFormat DEFAULT_TIME_FORMAT = DateFormat.getTimeInstance();
+
+       protected static final DateFormat DEFAULT_DATETIME_FORMAT = DateFormat.getDateTimeInstance();
+
+       protected static final DateFormat ISO_8601_FORMAT
+               = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+       protected static final DateFormat ISO_8601_FORMAT_WITH_MILLIS
+               = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
        private static boolean MillisAddedToTimeFormat = false;
-       private static final DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-       private static final DateFormat ISO_8601_FORMAT_WITH_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
-       private static final DateFormat ISO_8601_FORMAT_NOZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-       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|[\\+-]\\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 long SECS_SINCE_GARTRIP = 0L;
-       private static long MSECS_SINCE_1970 = 0L;
-       private static long MSECS_SINCE_1990 = 0L;
-       private static long TWENTY_YEARS_IN_SECS = 0L;
-       private static final long GARTRIP_OFFSET = 631065600L;
+
 
        /** Possible formats for parsing and displaying timestamps */
        public enum Format
@@ -48,451 +33,110 @@ public class Timestamp
                ISO8601
        }
 
-       /** Identifier for the parsing strategy to use */
-       private enum ParseType
-       {
-               NONE,
-               ISO8601_FRACTIONAL,
-               LONG,
-               FIXED_FORMAT0,
-               FIXED_FORMAT1,
-               FIXED_FORMAT2,
-               FIXED_FORMAT3,
-               FIXED_FORMAT4,
-               FIXED_FORMAT5,
-               FIXED_FORMAT6,
-               FIXED_FORMAT7,
-               FIXED_FORMAT8,
-               GENERAL_STRING
-       }
-
-       /** Array of parse types to loop through (first one is changed to last successful type) */
-       private static ParseType[] ALL_PARSE_TYPES = {ParseType.NONE, ParseType.ISO8601_FRACTIONAL, ParseType.LONG,
-               ParseType.FIXED_FORMAT0, ParseType.FIXED_FORMAT1, ParseType.FIXED_FORMAT2, ParseType.FIXED_FORMAT3,
-               ParseType.FIXED_FORMAT4, ParseType.FIXED_FORMAT5, ParseType.FIXED_FORMAT6, ParseType.FIXED_FORMAT7,
-               ParseType.FIXED_FORMAT8, ParseType.GENERAL_STRING};
 
-       // Static block to initialise offsets
+       // Static block to initialise date formats
        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;
                // Set timezone for output
+               TimeZone gmtZone = TimeZone.getTimeZone("GMT");
                ISO_8601_FORMAT.setTimeZone(gmtZone);
                ISO_8601_FORMAT_WITH_MILLIS.setTimeZone(gmtZone);
                DEFAULT_DATETIME_FORMAT.setTimeZone(gmtZone);
-               // Date formats
-               ALL_DATE_FORMATS = new DateFormat[] {
-                       DEFAULT_DATETIME_FORMAT,
-                       new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"),
-                       new SimpleDateFormat("HH:mm:ss dd MMM yyyy"),
-                       new SimpleDateFormat("dd MMM yyyy HH:mm:ss"),
-                       new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss"),
-                       new SimpleDateFormat("yyyy MMM dd HH:mm:ss"),
-                       new SimpleDateFormat("MMM dd, yyyy hh:mm:ss aa"),
-                       ISO_8601_FORMAT, ISO_8601_FORMAT_NOZ
-               };
-               for (DateFormat df : ALL_DATE_FORMATS) {
-                       df.setLenient(false);
-               }
-       }
-
-
-       /**
-        * Constructor
-        * @param inString String containing timestamp
-        */
-       public Timestamp(String inString)
-       {
-               _valid = false;
-               _text = null;
-               if (inString != null && !inString.equals(""))
-               {
-                       // Try each of the parse types in turn
-                       for (ParseType type : ALL_PARSE_TYPES)
-                       {
-                               if (parseString(inString, type))
-                               {
-                                       ALL_PARSE_TYPES[0] = type;
-                                       _valid = true;
-                                       _text = inString;
-                                       return;
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Try to parse the given string in the specified way
-        * @param inString String to parse
-        * @param inType parse type to use
-        * @return true if successful
-        */
-       private boolean parseString(String inString, ParseType inType)
-       {
-               if (inString == null || inString.equals("")) {
-                       return false;
-               }
-               switch (inType)
-               {
-                       case NONE: return false;
-                       case LONG:
-                               // Try to parse into a long
-                               try
-                               {
-                                       long rawValue = Long.parseLong(inString.trim());
-                                       _milliseconds = getMilliseconds(rawValue);
-                                       return true;
-                               }
-                               catch (NumberFormatException nfe)
-                               {}
-                               break;
-
-                       case ISO8601_FRACTIONAL:
-                               final Matcher fmatcher = ISO8601_FRACTIONAL_PATTERN.matcher(inString);
-                               if (fmatcher.matches())
-                               {
-                                       try {
-                                               _milliseconds = getMilliseconds(Integer.parseInt(fmatcher.group(1)), // year
-                                                       Integer.parseInt(fmatcher.group(2)), // month
-                                                       Integer.parseInt(fmatcher.group(3)), // day
-                                                       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(8));                  // timezone, if any
-                                               return true;
-                                       }
-                                       catch (NumberFormatException nfe) {}
-                               }
-                               break;
-
-                       case FIXED_FORMAT0: return parseString(inString, ALL_DATE_FORMATS[0]);
-                       case FIXED_FORMAT1: return parseString(inString, ALL_DATE_FORMATS[1]);
-                       case FIXED_FORMAT2: return parseString(inString, ALL_DATE_FORMATS[2]);
-                       case FIXED_FORMAT3: return parseString(inString, ALL_DATE_FORMATS[3]);
-                       case FIXED_FORMAT4: return parseString(inString, ALL_DATE_FORMATS[4]);
-                       case FIXED_FORMAT5: return parseString(inString, ALL_DATE_FORMATS[5]);
-                       case FIXED_FORMAT6: return parseString(inString, ALL_DATE_FORMATS[6]);
-                       case FIXED_FORMAT7: return parseString(inString, ALL_DATE_FORMATS[7]);
-                       case FIXED_FORMAT8: return parseString(inString, ALL_DATE_FORMATS[8]);
-
-                       case GENERAL_STRING:
-                               if (inString.length() == 19)
-                               {
-                                       final Matcher matcher = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
-                                       if (matcher.matches())
-                                       {
-                                               try {
-                                                       _milliseconds = getMilliseconds(Integer.parseInt(matcher.group(1)),
-                                                               Integer.parseInt(matcher.group(2)),
-                                                               Integer.parseInt(matcher.group(3)),
-                                                               Integer.parseInt(matcher.group(4)),
-                                                               Integer.parseInt(matcher.group(5)),
-                                                               Integer.parseInt(matcher.group(6)),
-                                                               null, null); // no fractions of a second and no timezone
-                                                       return true;
-                                               }
-                                               catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
-                                       }
-                               }
-                               return false;
-               }
-               return false;
        }
 
 
        /**
-        * Try to parse the given string with the given date format
-        * @param inString String to parse
-        * @param inDateFormat Date format to use
-        * @return true if successful
+        * @return true if valid
         */
-       private boolean parseString(String inString, DateFormat inDateFormat)
-       {
-               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
-               {
-                       CALENDAR.setTime(date);
-                       _milliseconds = CALENDAR.getTimeInMillis();
-                       return true;
-               }
-
-               return false;
-       }
-
+       public abstract boolean isValid();
 
        /**
-        * Constructor giving each field value individually
-        * @param inYear year
-        * @param inMonth month, beginning with 1
-        * @param inDay day of month, beginning with 1
-        * @param inHour hour of day, 0-24
-        * @param inMinute minute
-        * @param inSecond seconds
+        * Get a calendar representing this timestamp
         */
-       public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
-       {
-               _milliseconds = getMilliseconds(inYear, inMonth, inDay, inHour, inMinute, inSecond, null, null);
-               _valid = true;
-       }
-
+       public abstract Calendar getCalendar(TimeZone inZone);
 
        /**
-        * Constructor giving millis
-        * @param inMillis milliseconds since 1970
+        * @return the milliseconds according to the given timezone
         */
-       public Timestamp(long inMillis)
-       {
-               _milliseconds = inMillis;
-               _valid = true;
-       }
-
+       public abstract long getMilliseconds(TimeZone inZone);
 
        /**
-        * Convert the given timestamp parameters into a number of milliseconds
-        * @param inYear year
-        * @param inMonth month, beginning with 1
-        * @param inDay day of month, beginning with 1
-        * @param inHour hour of day, 0-24
-        * @param inMinute minute
-        * @param inSecond seconds
-        * @param inFraction fractions of a second
-        * @param inTimezone timezone, if any
-        * @return number of milliseconds
+        * @return true if this timestamp is after the other one
         */
-       private static long getMilliseconds(int inYear, int inMonth, int inDay,
-               int inHour, int inMinute, int inSecond, String inFraction, String inTimezone)
+       public boolean isAfter(Timestamp inOther)
        {
-               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.HOUR_OF_DAY, inHour);
-               cal.set(Calendar.MINUTE, inMinute);
-               cal.set(Calendar.SECOND, inSecond);
-               int millis = 0;
-               if (inFraction != null)
-               {
-                       try {
-                               int frac = Integer.parseInt(inFraction);
-                               final int fracLen = inFraction.length();
-                               switch (fracLen) {
-                                       case 1: millis = frac * 100; break;
-                                       case 2: millis = frac * 10;  break;
-                                       case 3: millis = frac;       break;
-                               }
-                       }
-                       catch (NumberFormatException nfe) {} // ignore errors, millis stay at 0
-               }
-               cal.set(Calendar.MILLISECOND, millis);
-               return cal.getTimeInMillis();
+               return getMillisecondsSince(inOther) > 0;
        }
 
        /**
-        * Convert the given long parameters into a number of millisseconds
-        * @param inRawValue long value representing seconds / milliseconds
-        * @return number of milliseconds
+        * @return true if this timestamp is before the other one
         */
-       private static long getMilliseconds(long inRawValue)
-       {
-               // check for each format possibility and pick nearest
-               long diff1 = Math.abs(SECS_SINCE_1970 - inRawValue);
-               long diff2 = Math.abs(MSECS_SINCE_1970 - inRawValue);
-               long diff3 = Math.abs(MSECS_SINCE_1990 - inRawValue);
-               long diff4 = Math.abs(SECS_SINCE_GARTRIP - inRawValue);
-
-               // Start off with "seconds since 1970" format
-               long smallestDiff = diff1;
-               long millis = inRawValue * 1000;
-               // Now check millis since 1970
-               if (diff2 < smallestDiff)
-               {
-                       // milliseconds since 1970
-                       millis = inRawValue;
-                       smallestDiff = diff2;
-               }
-               // Now millis since 1990
-               if (diff3 < smallestDiff)
-               {
-                       // milliseconds since 1990
-                       millis = inRawValue + TWENTY_YEARS_IN_SECS * 1000L;
-                       smallestDiff = diff3;
-               }
-               // Lastly, check gartrip offset
-               if (diff4 < smallestDiff)
-               {
-                       // seconds since gartrip offset
-                       millis = (inRawValue + GARTRIP_OFFSET) * 1000L;
-               }
-               return millis;
-       }
-
-       /**
-        * @return true if timestamp is valid
-        */
-       public boolean isValid()
+       public boolean isBefore(Timestamp inOther)
        {
-               return _valid;
+               return getMillisecondsSince(inOther) < 0;
        }
 
        /**
-        * @return true if the timestamp has non-zero milliseconds
-        */
-       public boolean hasMilliseconds()
-       {
-               return isValid() && (_milliseconds % 1000L) > 0;
-       }
-       /**
-        * @param inOther other Timestamp
-        * @return true if this one is at least a millisecond after the other
+        * @return true if this timestamp is equal to the other one
         */
-       public boolean isAfter(Timestamp inOther)
+       public boolean isEqual(Timestamp inOther)
        {
-               return getMillisecondsSince(inOther) > 0L;
+               return getMillisecondsSince(inOther) == 0;
        }
 
        /**
-        * Calculate the difference between two Timestamps in seconds
-        * @param inOther other, earlier Timestamp
-        * @return number of seconds since other timestamp
+        * @return the number of seconds since the other timestamp
         */
        public long getSecondsSince(Timestamp inOther)
        {
-               return (_milliseconds - inOther._milliseconds) / 1000L;
+               return getMillisecondsSince(inOther) / 1000L;
        }
 
        /**
         * Calculate the difference between two Timestamps in milliseconds
         * @param inOther other, earlier Timestamp
-        * @return number of millisseconds since other timestamp
+        * @return number of milliseconds since other timestamp
         */
        public long getMillisecondsSince(Timestamp inOther)
        {
-               return _milliseconds - inOther._milliseconds;
+               return getMilliseconds(null) - inOther.getMilliseconds(null);
        }
 
        /**
-        * @param inOther other timestamp to compare
-        * @return true if they're equal to the nearest millisecond
+        * @return the number of seconds since the other timestamp using the given timezone
         */
-       public boolean isEqual(Timestamp inOther)
+       public long getSecondsSince(Timestamp inOther, TimeZone inTimezone)
        {
-               return inOther != null && _milliseconds == inOther._milliseconds;
-       }
-
-       /**
-        * @param inOther other Timestamp
-        * @return true if this one is before the other
-        */
-       public boolean isBefore(Timestamp inOther)
-       {
-               return getMillisecondsSince(inOther) < 0L;
+               return (getMilliseconds(inTimezone) - inOther.getMilliseconds(inTimezone)) / 1000L;
        }
 
        /**
         * Add the given number of seconds offset
         * @param inOffset number of seconds to add/subtract
         */
-       public void addOffset(long inOffset)
-       {
-               _milliseconds += (inOffset * 1000L);
-               _text = null;
-       }
+       public abstract void addOffsetSeconds(long inOffset);
 
        /**
-        * Add the given TimeDifference to this Timestamp
-        * @param inOffset TimeDifference to add
-        * @return new Timestamp object
-        */
-       public Timestamp createPlusOffset(TimeDifference inOffset)
-       {
-               return createPlusOffset(inOffset.getTotalSeconds());
-       }
-
-       /**
-        * Add the given number of seconds to this Timestamp
-        * @param inSeconds number of seconds to add
-        * @return new Timestamp object
-        */
-       public Timestamp createPlusOffset(long inSeconds)
-       {
-               return new Timestamp(_milliseconds + (inSeconds * 1000L));
-       }
-
-
-       /**
-        * Subtract the given TimeDifference from this Timestamp
-        * @param inOffset TimeDifference to subtract
-        * @return new Timestamp object
-        */
-       public Timestamp createMinusOffset(TimeDifference inOffset)
-       {
-               return new Timestamp(_milliseconds - (inOffset.getTotalSeconds() * 1000L));
-       }
-
-
-       /**
-        * @return Description of timestamp in locale-specific format
+        * @return true if the timestamp has non-zero milliseconds
         */
-       public String getText()
-       {
-               return getText(Format.LOCALE);
-       }
+       protected abstract boolean hasMilliseconds();
 
-       /**
-        * @param inFormat format of timestamp
-        * @return Description of timestamp in required format
-        */
-       public String getText(Format inFormat)
-       {
-               if (!_valid) {return "";}
-               switch (inFormat)
-               {
-                       case ORIGINAL:
-                               if (_text != null) {return _text;}
-                               // otherwise fallthrough to default
-                               //$FALL-THROUGH$
-                       case LOCALE:
-                               return format(DEFAULT_DATETIME_FORMAT);
-                       case ISO8601:
-                               return format(hasMilliseconds() ? ISO_8601_FORMAT_WITH_MILLIS : ISO_8601_FORMAT);
-               }
-               return _text;
-       }
 
        /**
         * @return date part of timestamp in locale-specific format
         */
-       public String getDateText()
+       public String getDateText(TimeZone inTimezone)
        {
-               if (!_valid) return "";
-               return format(DEFAULT_DATE_FORMAT);
+               if (!isValid()) return "";
+               return format(DEFAULT_DATE_FORMAT, inTimezone);
        }
 
        /**
         * @return Description of time part of timestamp in locale-specific format
         */
-       public String getTimeText()
+       public String getTimeText(TimeZone inTimezone)
        {
-               if (!_valid) return "";
+               if (!isValid()) return "";
                // Maybe we should add milliseconds to this format?
                if (hasMilliseconds() && !MillisAddedToTimeFormat)
                {
@@ -508,29 +152,42 @@ public class Timestamp
                        }
                        catch (ClassCastException cce) {}
                }
-               return format(DEFAULT_TIME_FORMAT);
+               return format(DEFAULT_TIME_FORMAT, inTimezone);
        }
 
        /**
         * Utility method for formatting dates / times
-        * @param inFormat formatter object
-        * @return formatted String
         */
-       private String format(DateFormat inFormat)
+       protected abstract String format(DateFormat inFormat, TimeZone inTimezone);
+
+
+       /**
+        * @return Description of timestamp in locale-specific format
+        */
+       public String getText(TimeZone inTimezone)
        {
-               CALENDAR.setTimeZone(TimeZone.getTimeZone("GMT"));
-               CALENDAR.setTimeInMillis(_milliseconds);
-               return inFormat.format(CALENDAR.getTime());
+               return getText(Format.LOCALE, inTimezone);
        }
 
        /**
-        * @return a Calendar object representing this timestamp
+        * @param inFormat format of timestamp
+        * @return Description of timestamp in required format
         */
-       public Calendar getCalendar()
+       public String getText(Format inFormat, TimeZone inTimezone)
        {
-               Calendar cal = Calendar.getInstance();
-               cal.setTimeZone(TimeZone.getTimeZone("GMT"));
-               cal.setTimeInMillis(_milliseconds);
-               return cal;
+               if (!isValid()) {
+                       return "";
+               }
+               switch (inFormat)
+               {
+                       case ORIGINAL:
+                       case LOCALE:
+                       default:
+                               return format(DEFAULT_DATETIME_FORMAT, inTimezone);
+                       case ISO8601:
+                               return format(hasMilliseconds() ? ISO_8601_FORMAT_WITH_MILLIS : ISO_8601_FORMAT,
+                                       inTimezone);
+               }
        }
+
 }
diff --git a/tim/prune/data/TimestampLocal.java b/tim/prune/data/TimestampLocal.java
new file mode 100644 (file)
index 0000000..d4c9093
--- /dev/null
@@ -0,0 +1,104 @@
+package tim.prune.data;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+
+/**
+ * Class to hold a timestamp based on a local timezone, for example
+ * from a camera or audio recorder.
+ * When the selected timezone changes, this timestamp will keep its
+ * date and time but the numerical value will change accordingly.
+ */
+public class TimestampLocal extends Timestamp
+{
+       private boolean _valid = false;
+       private int _year=0, _month=0, _day=0;
+       private int _hour=0, _minute=0, _second=0;
+
+
+       /**
+        * Constructor giving each field value individually
+        * @param inYear year
+        * @param inMonth month, beginning with 1
+        * @param inDay day of month, beginning with 1
+        * @param inHour hour of day, 0-24
+        * @param inMinute minute
+        * @param inSecond seconds
+        */
+       public TimestampLocal(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
+       {
+               _valid = inYear > 0 && inYear < 3000
+                       && inMonth > 0 && inMonth < 13
+                       && inDay > 0 && inDay < 32
+                       && inHour >= 0 && inHour < 24
+                       && inMinute >= 0 && inMinute < 60
+                       && inSecond >= 0 && inSecond < 60;
+               if (_valid)
+               {
+                       _year = inYear;
+                       _month = inMonth;
+                       _day = inDay;
+                       _hour = inHour;
+                       _minute = inMinute;
+                       _second = inSecond;
+               }
+       }
+
+
+       /** @return true if valid */
+       public boolean isValid()
+       {
+               return _valid;
+       }
+
+       @Override
+       public Calendar getCalendar(TimeZone inZone)
+       {
+               Calendar cal = Calendar.getInstance();
+               if (inZone != null) {
+                       cal.setTimeZone(inZone);
+               }
+               cal.set(Calendar.YEAR, _year);
+               cal.set(Calendar.MONTH, _month - 1);
+               cal.set(Calendar.DAY_OF_MONTH, _day);
+               cal.set(Calendar.HOUR_OF_DAY, _hour);
+               cal.set(Calendar.MINUTE, _minute);
+               cal.set(Calendar.SECOND, _second);
+               cal.set(Calendar.MILLISECOND, 0);
+               return cal;
+       }
+
+       @Override
+       public long getMilliseconds(TimeZone inZone)
+       {
+               return getCalendar(inZone).getTimeInMillis();
+       }
+
+       @Override
+       public void addOffsetSeconds(long inOffset)
+       {
+               System.err.println("Local timestamps don't support offsets.");
+       }
+
+       @Override
+       protected boolean hasMilliseconds()
+       {
+               return false;
+       }
+
+       /**
+        * Utility method for formatting dates / times
+        * @param inFormat formatter object
+        * @param inTimezone timezone to use
+        * @return formatted String
+        */
+       @Override
+       protected String format(DateFormat inFormat, TimeZone inTimezone)
+       {
+               Calendar cal = getCalendar(inTimezone);
+               inFormat.setTimeZone(inTimezone);
+               return inFormat.format(cal.getTime());
+       }
+}
diff --git a/tim/prune/data/TimestampUtc.java b/tim/prune/data/TimestampUtc.java
new file mode 100644 (file)
index 0000000..938123b
--- /dev/null
@@ -0,0 +1,399 @@
+package tim.prune.data;
+
+import java.text.DateFormat;
+import java.text.ParsePosition;
+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;
+
+
+/**
+ * Class to hold a UTC-based timestamp, for example of a track point.
+ * When the selected timezone changes, this timestamp will keep its
+ * numerical value but the date and time will change accordingly.
+ */
+public class TimestampUtc extends Timestamp
+{
+       private boolean _valid = false;
+       private long _milliseconds = 0L;
+       private String _text = null;
+
+       private static final DateFormat ISO_8601_FORMAT_NOZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+       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|[\\+-]\\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 long SECS_SINCE_GARTRIP = 0L;
+       private static long MSECS_SINCE_1970 = 0L;
+       private static long MSECS_SINCE_1990 = 0L;
+       private static long TWENTY_YEARS_IN_SECS = 0L;
+       private static final long GARTRIP_OFFSET = 631065600L;
+
+       /** Identifier for the parsing strategy to use */
+       private enum ParseType
+       {
+               NONE,
+               ISO8601_FRACTIONAL,
+               LONG,
+               FIXED_FORMAT0,
+               FIXED_FORMAT1,
+               FIXED_FORMAT2,
+               FIXED_FORMAT3,
+               FIXED_FORMAT4,
+               FIXED_FORMAT5,
+               FIXED_FORMAT6,
+               FIXED_FORMAT7,
+               FIXED_FORMAT8,
+               GENERAL_STRING
+       }
+
+       /** Array of parse types to loop through (first one is changed to last successful type) */
+       private static ParseType[] ALL_PARSE_TYPES = {ParseType.NONE, ParseType.ISO8601_FRACTIONAL, ParseType.LONG,
+               ParseType.FIXED_FORMAT0, ParseType.FIXED_FORMAT1, ParseType.FIXED_FORMAT2, ParseType.FIXED_FORMAT3,
+               ParseType.FIXED_FORMAT4, ParseType.FIXED_FORMAT5, ParseType.FIXED_FORMAT6, ParseType.FIXED_FORMAT7,
+               ParseType.FIXED_FORMAT8, ParseType.GENERAL_STRING};
+
+       // Static block to initialise offsets
+       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;
+               // Date formats
+               ALL_DATE_FORMATS = new DateFormat[]
+               {
+                       DEFAULT_DATETIME_FORMAT,
+                       new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"),
+                       new SimpleDateFormat("HH:mm:ss dd MMM yyyy"),
+                       new SimpleDateFormat("dd MMM yyyy HH:mm:ss"),
+                       new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss"),
+                       new SimpleDateFormat("yyyy MMM dd HH:mm:ss"),
+                       new SimpleDateFormat("MMM dd, yyyy hh:mm:ss aa"),
+                       ISO_8601_FORMAT, ISO_8601_FORMAT_NOZ
+               };
+               for (DateFormat df : ALL_DATE_FORMATS)
+               {
+                       df.setLenient(false);
+                       df.setTimeZone(gmtZone);
+               }
+       }
+
+
+       /**
+        * Constructor
+        * @param inString String containing timestamp
+        */
+       public TimestampUtc(String inString)
+       {
+               _valid = false;
+               _text = null;
+               if (inString != null && !inString.equals(""))
+               {
+                       // Try each of the parse types in turn
+                       for (ParseType type : ALL_PARSE_TYPES)
+                       {
+                               if (parseString(inString, type))
+                               {
+                                       ALL_PARSE_TYPES[0] = type;
+                                       _valid = true;
+                                       _text = inString;
+                                       return;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Try to parse the given string in the specified way
+        * @param inString String to parse
+        * @param inType parse type to use
+        * @return true if successful
+        */
+       private boolean parseString(String inString, ParseType inType)
+       {
+               if (inString == null || inString.equals("")) {
+                       return false;
+               }
+               switch (inType)
+               {
+                       case NONE: return false;
+                       case LONG:
+                               // Try to parse into a long
+                               try
+                               {
+                                       long rawValue = Long.parseLong(inString.trim());
+                                       _milliseconds = getMilliseconds(rawValue);
+                                       return true;
+                               }
+                               catch (NumberFormatException nfe)
+                               {}
+                               break;
+
+                       case ISO8601_FRACTIONAL:
+                               final Matcher fmatcher = ISO8601_FRACTIONAL_PATTERN.matcher(inString);
+                               if (fmatcher.matches())
+                               {
+                                       try {
+                                               _milliseconds = getMilliseconds(Integer.parseInt(fmatcher.group(1)), // year
+                                                       Integer.parseInt(fmatcher.group(2)), // month
+                                                       Integer.parseInt(fmatcher.group(3)), // day
+                                                       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(8));                  // timezone, if any
+                                               return true;
+                                       }
+                                       catch (NumberFormatException nfe) {}
+                               }
+                               break;
+
+                       case FIXED_FORMAT0: return parseString(inString, ALL_DATE_FORMATS[0]);
+                       case FIXED_FORMAT1: return parseString(inString, ALL_DATE_FORMATS[1]);
+                       case FIXED_FORMAT2: return parseString(inString, ALL_DATE_FORMATS[2]);
+                       case FIXED_FORMAT3: return parseString(inString, ALL_DATE_FORMATS[3]);
+                       case FIXED_FORMAT4: return parseString(inString, ALL_DATE_FORMATS[4]);
+                       case FIXED_FORMAT5: return parseString(inString, ALL_DATE_FORMATS[5]);
+                       case FIXED_FORMAT6: return parseString(inString, ALL_DATE_FORMATS[6]);
+                       case FIXED_FORMAT7: return parseString(inString, ALL_DATE_FORMATS[7]);
+                       case FIXED_FORMAT8: return parseString(inString, ALL_DATE_FORMATS[8]);
+
+                       case GENERAL_STRING:
+                               if (inString.length() == 19)
+                               {
+                                       final Matcher matcher = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
+                                       if (matcher.matches())
+                                       {
+                                               try {
+                                                       _milliseconds = getMilliseconds(Integer.parseInt(matcher.group(1)),
+                                                               Integer.parseInt(matcher.group(2)),
+                                                               Integer.parseInt(matcher.group(3)),
+                                                               Integer.parseInt(matcher.group(4)),
+                                                               Integer.parseInt(matcher.group(5)),
+                                                               Integer.parseInt(matcher.group(6)),
+                                                               null, null); // no fractions of a second and no timezone
+                                                       return true;
+                                               }
+                                               catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
+                                       }
+                               }
+                               return false;
+               }
+               return false;
+       }
+
+
+       /**
+        * Try to parse the given string with the given date format
+        * @param inString String to parse
+        * @param inDateFormat Date format to use
+        * @return true if successful
+        */
+       private boolean parseString(String inString, DateFormat inDateFormat)
+       {
+               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
+               {
+                       CALENDAR.setTime(date);
+                       _milliseconds = CALENDAR.getTimeInMillis();
+                       return true;
+               }
+
+               return false;
+       }
+
+
+       /**
+        * Constructor giving millis
+        * @param inMillis milliseconds since 1970
+        */
+       public TimestampUtc(long inMillis)
+       {
+               _milliseconds = inMillis;
+               _valid = true;
+       }
+
+
+       /**
+        * Convert the given timestamp parameters into a number of milliseconds
+        * @param inYear year
+        * @param inMonth month, beginning with 1
+        * @param inDay day of month, beginning with 1
+        * @param inHour hour of day, 0-24
+        * @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,
+               int inHour, int inMinute, int inSecond, String inFraction, String inTimezone)
+       {
+               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.HOUR_OF_DAY, inHour);
+               cal.set(Calendar.MINUTE, inMinute);
+               cal.set(Calendar.SECOND, inSecond);
+               int millis = 0;
+               if (inFraction != null)
+               {
+                       try {
+                               int frac = Integer.parseInt(inFraction);
+                               final int fracLen = inFraction.length();
+                               switch (fracLen) {
+                                       case 1: millis = frac * 100; break;
+                                       case 2: millis = frac * 10;  break;
+                                       case 3: millis = frac;       break;
+                               }
+                       }
+                       catch (NumberFormatException nfe) {} // ignore errors, millis stay at 0
+               }
+               cal.set(Calendar.MILLISECOND, millis);
+               return cal.getTimeInMillis();
+       }
+
+       /**
+        * Convert the given long parameters into a number of milliseconds
+        * @param inRawValue long value representing seconds / milliseconds
+        * @return number of milliseconds
+        */
+       private static long getMilliseconds(long inRawValue)
+       {
+               // check for each format possibility and pick nearest
+               long diff1 = Math.abs(SECS_SINCE_1970 - inRawValue);
+               long diff2 = Math.abs(MSECS_SINCE_1970 - inRawValue);
+               long diff3 = Math.abs(MSECS_SINCE_1990 - inRawValue);
+               long diff4 = Math.abs(SECS_SINCE_GARTRIP - inRawValue);
+
+               // Start off with "seconds since 1970" format
+               long smallestDiff = diff1;
+               long millis = inRawValue * 1000;
+               // Now check millis since 1970
+               if (diff2 < smallestDiff)
+               {
+                       // milliseconds since 1970
+                       millis = inRawValue;
+                       smallestDiff = diff2;
+               }
+               // Now millis since 1990
+               if (diff3 < smallestDiff)
+               {
+                       // milliseconds since 1990
+                       millis = inRawValue + TWENTY_YEARS_IN_SECS * 1000L;
+                       smallestDiff = diff3;
+               }
+               // Lastly, check gartrip offset
+               if (diff4 < smallestDiff)
+               {
+                       // seconds since gartrip offset
+                       millis = (inRawValue + GARTRIP_OFFSET) * 1000L;
+               }
+               return millis;
+       }
+
+       /**
+        * @return true if timestamp is valid
+        */
+       public boolean isValid()
+       {
+               return _valid;
+       }
+
+       /**
+        * @return true if the timestamp has non-zero milliseconds
+        */
+       protected boolean hasMilliseconds()
+       {
+               return isValid() && (_milliseconds % 1000L) > 0;
+       }
+
+       /**
+        * @return the milliseconds according to the given timezone
+        */
+       public long getMilliseconds(TimeZone inZone)
+       {
+               return _milliseconds;
+       }
+
+
+       /**
+        * Add the given number of seconds offset
+        * @param inOffset number of seconds to add/subtract
+        */
+       public void addOffsetSeconds(long inOffset)
+       {
+               _milliseconds += (inOffset * 1000L);
+               _text = null;
+       }
+
+
+       /**
+        * @param inFormat format of timestamp
+        * @param inTimezone timezone to use
+        * @return Description of timestamp in required format
+        */
+       public String getText(Format inFormat, TimeZone inTimezone)
+       {
+               // Use the cached text if possible
+               if (isValid()
+                       && _text != null
+                       && inFormat == Format.ORIGINAL)
+               {
+                       return _text;
+               }
+               
+               // Nothing cached, so use the regular one
+               return super.getText(inFormat, inTimezone);
+       }
+
+       /**
+        * Utility method for formatting dates / times
+        * @param inFormat formatter object
+        * @param inTimezone timezone to use
+        * @return formatted String
+        */
+       protected String format(DateFormat inFormat, TimeZone inTimezone)
+       {
+               CALENDAR.setTimeZone(TimeZone.getTimeZone("GMT"));
+               inFormat.setTimeZone(inTimezone == null ? TimeZone.getTimeZone("GMT") : inTimezone);
+
+               CALENDAR.setTimeInMillis(_milliseconds);
+               return inFormat.format(CALENDAR.getTime());
+       }
+
+       /**
+        * @return a Calendar object representing this timestamp
+        */
+       public Calendar getCalendar(TimeZone inZone)
+       {
+               Calendar cal = Calendar.getInstance();
+               cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+               cal.setTimeInMillis(_milliseconds);
+               return cal;
+       }
+}
index 4ae47ff03e78bc794bcb89cfd289e30006bfa5e9..f45854a0e3d47a1fd5fc0ee6924732b3294decc0 100644 (file)
@@ -320,7 +320,7 @@ public class Track
         * @param inUndo true for undo operation
         * @return true on success
         */
-       public boolean addTimeOffset(int inStart, int inEnd, long inOffset, boolean inUndo)
+       public boolean addTimeOffsetSeconds(int inStart, int inEnd, long inOffset, boolean inUndo)
        {
                // sanity check
                if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
@@ -335,7 +335,7 @@ public class Track
                        {
                                // This point has a timestamp so add the offset to it
                                foundTimestamp = true;
-                               p.addTimeOffset(inOffset);
+                               p.addTimeOffsetSeconds(inOffset);
                                p.setModified(inUndo);
                        }
                }
index d2f571250660664deabb6c7be0ae3c4c1a09e869..80a7983418149d97f3cf7fce1e9915cda968dae2 100644 (file)
@@ -34,7 +34,6 @@ import tim.prune.ExternalTools;
 import tim.prune.GenericFunction;
 import tim.prune.GpsPrune;
 import tim.prune.I18nManager;
-import tim.prune.jpeg.ExifGateway;
 import tim.prune.threedee.WindowFactory;
 
 /**
@@ -98,8 +97,8 @@ public class AboutScreen extends GenericFunction
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.summarytext3")).append("</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.languages")).append(" : ")
                        .append("afrikaans, \u010de\u0161tina, deutsch, english, espa\u00F1ol, fran\u00E7ais, italiano,<br>" +
-                               " magyar, nederlands, polski, portugu\u00EAs, rom\u00E2n\u0103, \u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian), \u4e2d\u6587 (chinese),<br>" +
-                               " \u65E5\u672C\u8A9E (japanese), \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, t\u00FCrk\u00E7e, ukrainian</p>");
+                               " magyar, nederlands, polski, portugu\u00EAs, rom\u00E2n\u0103, suomi, \u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian),<br>" +
+                               " \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese), \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch, ukrainian</p>");
                descBuffer.append("<p>").append(I18nManager.getText("dialog.about.translatedby")).append("</p>");
                JEditorPane descPane = new JEditorPane("text/html", descBuffer.toString());
                descPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
@@ -153,13 +152,6 @@ public class AboutScreen extends GenericFunction
                addToGridBagPanel(sysInfoPanel, gridBag, constraints, _installedLabels[3], 1, 5);
                addToGridBagPanel(sysInfoPanel, gridBag, constraints, new JLabel("Xerces : "), 0, 6);
                addToGridBagPanel(sysInfoPanel, gridBag, constraints, _installedLabels[4], 1, 6);
-               // Exif library
-               addToGridBagPanel(sysInfoPanel, gridBag, constraints,
-                       new JLabel(I18nManager.getText("dialog.about.systeminfo.exiflib") + " : "),
-                       0, 7);
-               final String exiflibkey = "dialog.about.systeminfo.exiflib." + ExifGateway.getDescriptionKey();
-               addToGridBagPanel(sysInfoPanel, gridBag, constraints,
-                       new JLabel(I18nManager.getText(exiflibkey)), 1, 7);
                _tabs.add(I18nManager.getText("dialog.about.systeminfo"), sysInfoPanel);
 
                // Third pane for credits
@@ -198,35 +190,38 @@ public class AboutScreen extends GenericFunction
                        new JLabel(" theYinYeti, Rothermographer, Sam, Rudolph, nazotoko,"),
                        1, 4);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
-                       new JLabel(" katpatuka, R\u00E9mi, Marcus, Ali, Javier, Jeroen, prot_d, Gy\u00F6rgy,"),
+                       new JLabel(" katpatuka, R\u00E9mi, Marcus, Ali, Javier, Jeroen, prot_d,"),
                        1, 5);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
-                       new JLabel(" HooAU, Sergey, P\u00E9ter, serhijdubyk, Peter, Cristian, Roman"),
+                       new JLabel(" Gy\u00F6rgy, HooAU, Sergey, P\u00E9ter, serhijdubyk, Peter, Cristian,"),
                        1, 6);
+               addToGridBagPanel(creditsPanel, gridBag, constraints,
+                       new JLabel(" Roman, Erkki"),
+                       1, 7);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "),
-                       0, 7);
+                       0, 8);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel("Open Office, Gpsdrive, Babelfish, Leo, Launchpad"),
-                       1, 7);
+                       1, 8);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.devtools") + " : "),
-                       0, 8);
+                       0, 9);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel("Debian Linux, Sun Java, OpenJDK, Eclipse, Svn, Gimp, Inkscape"),
-                       1, 8);
+                       1, 9);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.othertools") + " : "),
-                       0, 9);
+                       0, 10);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel("Openstreetmap, Povray, Exiftool, Gpsbabel, Gnuplot"),
-                       1, 9);
+                       1, 10);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel(I18nManager.getText("dialog.about.credits.thanks") + " : "),
-                       0, 10);
+                       0, 11);
                addToGridBagPanel(creditsPanel, gridBag, constraints,
                        new JLabel("Friends and loved ones, for encouragement and support"),
-                       1, 10);
+                       1, 11);
                _tabs.add(I18nManager.getText("dialog.about.credits"), creditsPanel);
 
                // Read me
index bbfda5ed699ea88d32e60d6683fffa7b08eaca09..5ccdc39d6d05782fcc6f1cbc8a81942190dce9a6 100644 (file)
@@ -118,8 +118,9 @@ public class AddTimeOffset extends GenericFunction
                // Listeners to enable/disable ok button
                KeyAdapter keyListener = new KeyAdapter() {
                        /** Key typed */
-                       public void keyTyped(KeyEvent arg0) {
-                               _okButton.setEnabled(getOffsetSecs() != 0L);
+                       public void keyTyped(KeyEvent event) {
+                               final boolean isNumber = "1234567890".indexOf(event.getKeyChar()) >= 0;
+                _okButton.setEnabled(isNumber || getOffsetSecs() != 0L);
                        }
                };
                MouseAdapter mouseListener = new MouseAdapter() {
@@ -194,7 +195,7 @@ public class AddTimeOffset extends GenericFunction
                if (offsetSecs != 0L)
                {
                        // Pass offset back to app and close dialog
-                       _app.finishAddTimeOffset(offsetSecs);
+                       _app.finishAddTimeOffsetSeconds(offsetSecs);
                        _dialog.dispose();
                }
        }
index 8c2394ed3f858ebab19a2ca334b8da5a1e38b3bf..701937d4b5f23066586cbab4dc55727195544907 100644 (file)
@@ -106,7 +106,7 @@ public class CheckVersionScreen extends GenericFunction
                                == JOptionPane.YES_OPTION)
                        {
                                // User selected to launch home page
-                               BrowserLauncher.launchBrowser("http://activityworkshop.net/software/gpsprune/download.html");
+                               BrowserLauncher.launchBrowser("https://activityworkshop.net/software/gpsprune/download.html");
                        }
                }
        }
index c0286ab6ca3419479454697f45e324f48ca53c00..2368dfd11fea277656b4c32eb8c826ad3bdc149f 100644 (file)
@@ -7,7 +7,7 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
-import tim.prune.data.Timestamp;
+import tim.prune.data.TimestampUtc;
 import tim.prune.data.Track;
 import tim.prune.undo.UndoConvertNamesToTimes;
 
@@ -51,8 +51,9 @@ public class ConvertNamesToTimes extends GenericFunction
                        DataPoint point = track.getPoint(i);
                        if (point.isWaypoint())
                        {
-                               Timestamp tstamp = new Timestamp(point.getWaypointName());
-                               if (tstamp.isValid()) {
+                               TimestampUtc tstamp = new TimestampUtc(point.getWaypointName());
+                               if (tstamp.isValid())
+                               {
                                        // timestamp could be parsed!
                                        point.setFieldValue(Field.TIMESTAMP, point.getWaypointName(), false);
                                        // set waypoint name to nothing (track point)
diff --git a/tim/prune/function/CreateMarkerWaypointsFunction.java b/tim/prune/function/CreateMarkerWaypointsFunction.java
new file mode 100644 (file)
index 0000000..f3dd1f1
--- /dev/null
@@ -0,0 +1,145 @@
+package tim.prune.function;
+
+import java.util.ArrayList;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Field;
+import tim.prune.data.FieldList;
+import tim.prune.data.Track;
+import tim.prune.undo.UndoAppendPoints;
+
+/**
+ * Function to create waypoints marking either
+ * at regular distance intervals or time intervals
+ */
+public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
+{
+       /** ArrayList of points to append to the track */
+       private ArrayList<DataPoint> _pointsToAdd = new ArrayList<DataPoint>();
+       /** Counter of previously used multiple */
+       private int _previousMultiple = 0;
+
+
+       /**
+        * Constructor
+        */
+       public CreateMarkerWaypointsFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.createmarkerwaypoints";
+       }
+
+       /**
+        * Init the state to start collecting a new set of points
+        */
+       private void initMemory()
+       {
+               _pointsToAdd.clear();
+               _previousMultiple = 0;
+       }
+
+       /**
+        * The dialog has been completed and OK pressed, so do the point creation
+        */
+       protected void performFunction()
+       {
+               // Distribute either by distance or time
+               final int timeLimitSeconds = getTimeLimitInSeconds();
+               final boolean createByTime = (timeLimitSeconds > 0);
+               final double distLimitRadians = getDistanceLimitRadians();
+               final boolean createByDistance = (distLimitRadians > 0.0);
+               if (!createByTime && !createByDistance) {
+                       return; // neither option selected
+               }
+
+               // Make undo object
+               final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+               UndoAppendPoints undo = new UndoAppendPoints(numPoints);
+
+               // set up the memory from scratch to collect the created points
+               initMemory();
+
+               // Make new waypoints, looping through the points in the track
+               DataPoint currPoint = null, prevPoint = null;
+               double currValue = 0.0, prevValue = 0.0;
+               for (int i=0; i<numPoints; i++)
+               {
+                       currPoint = _app.getTrackInfo().getTrack().getPoint(i);
+                       if (!currPoint.isWaypoint())
+                       {
+                               if (!currPoint.getSegmentStart() && prevPoint != null)
+                               {
+                                       // Calculate current value
+                                       if (createByTime)
+                                       {
+                                               if (currPoint.hasTimestamp() && prevPoint.hasTimestamp())
+                                               {
+                                                       currValue += (currPoint.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp()) / 1000.0);
+                                                       processValue(prevPoint, prevValue, timeLimitSeconds, currPoint, currValue);
+                                               }
+                                       }
+                                       else if (createByDistance)
+                                       {
+                                               currValue += DataPoint.calculateRadiansBetween(prevPoint, currPoint);
+                                               processValue(prevPoint, prevValue, distLimitRadians, currPoint, currValue);
+                                       }
+                               }
+                               prevPoint = currPoint;
+                               prevValue = currValue;
+                       }
+               }
+
+               // System.out.println(_pointsToAdd.size() + " markers to add...");
+               if (!_pointsToAdd.isEmpty())
+               {
+                       // Append created points to Track
+                       Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE, Field.WAYPT_NAME};
+                       final int numPointsToAdd = _pointsToAdd.size();
+                       DataPoint[] waypoints = new DataPoint[numPointsToAdd];
+                       _pointsToAdd.toArray(waypoints);
+                       Track wpTrack = new Track(new FieldList(fields), waypoints);
+                       _app.getTrackInfo().getTrack().combine(wpTrack);
+
+                       undo.setNumPointsAppended(numPointsToAdd);
+                       _app.completeFunction(undo, I18nManager.getText("confirm.interpolate"));
+                       // TODO: Maybe add new token including number of points added/created
+                       UpdateMessageBroker.informSubscribers();
+               }
+               _dialog.dispose();
+       }
+
+       /**
+        * Consider a pair of points in the track to see if a new marker should be inserted between them
+        * @param inPrevPoint previous point
+        * @param inPrevValue value of function at this previous point
+        * @param inLimit user-specified limit for marker values
+        * @param inCurrPoint current point
+        * @param inCurrValue value of function at this current point
+        */
+       private void processValue(DataPoint inPrevPoint, double inPrevValue, double inLimit,
+               DataPoint inCurrPoint, double inCurrValue)
+       {
+               // Check the current multiple and compare with previously used one
+               final int currMultiple = (int) Math.floor(inCurrValue / inLimit);
+               for (int m=_previousMultiple+1; m<=currMultiple; m++)
+               {
+                       // Calculate position of limit between the two points
+                       final double valueBeforeBreak = (m * inLimit) - inPrevValue;
+                       final double valueAfterBreak = inCurrValue - (m * inLimit);
+                       final double fractionFromPrev = valueBeforeBreak / (valueBeforeBreak + valueAfterBreak);
+                       DataPoint marker = DataPoint.interpolate(inPrevPoint, inCurrPoint, fractionFromPrev);
+                       marker.setFieldValue(Field.WAYPT_NAME, createLimitDescription(m), false);
+                       _pointsToAdd.add(marker);
+                       // System.out.println("I would add a point here with values " + inPrevValue + " and " + inCurrValue);
+               }
+               _previousMultiple = currMultiple;
+       }
+}
diff --git a/tim/prune/function/DistanceTimeLimitFunction.java b/tim/prune/function/DistanceTimeLimitFunction.java
new file mode 100644 (file)
index 0000000..eaa7b7d
--- /dev/null
@@ -0,0 +1,275 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.Distance;
+import tim.prune.data.Field;
+import tim.prune.data.TimeDifference;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * Superclass for functions which act on a defined
+ * distance limit or time limit
+ */
+public abstract class DistanceTimeLimitFunction extends GenericFunction
+{
+       /** Dialog */
+       protected JDialog _dialog = null;
+       /** Radio buttons for splitting by distance and time */
+       private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
+       /** Dropdown for selecting distance units */
+       private JComboBox<String> _distUnitsDropdown = null;
+       /** Text field for entering distance */
+       private WholeNumberField _distanceField = null;
+       /** Text fields for entering distance */
+       private WholeNumberField _limitHourField = null, _limitMinField = null;
+       /** Ok and cancel buttons */
+       private JButton _okButton = null;
+       private JButton _cancelButton = null;
+
+
+       /**
+        * React to item changes and key presses
+        */
+       private abstract class ChangeListener extends KeyAdapter implements ItemListener
+       {
+               /** Method to be implemented */
+               public abstract void optionsChanged();
+
+               /** Item changed in ItemListener */
+               public void itemStateChanged(ItemEvent arg0) {
+                       optionsChanged();
+               }
+
+               /** Key released in KeyListener */
+               public void keyReleased(KeyEvent arg0) {
+                       optionsChanged();
+               }
+       }
+
+       /**
+        * Constructor
+        */
+       public DistanceTimeLimitFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               enableOkButton();
+               // TODO: Maybe set distance units according to current Config setting?
+               final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
+               _timeLimitRadio.setEnabled(hasTimestamps);
+               if (!hasTimestamps)
+               {
+                       _distLimitRadio.setSelected(true);
+               }
+               // set focus to Cancel button so that pressing "Esc" works
+               _cancelButton.requestFocus();
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(5, 5));
+
+               // Make radio buttons for three different options
+               _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
+               _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
+               ButtonGroup radioGroup = new ButtonGroup();
+               radioGroup.add(_distLimitRadio);
+               radioGroup.add(_timeLimitRadio);
+
+               // central panel for limits
+               JPanel limitsPanel = new JPanel();
+               limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
+               limitsPanel.add(Box.createVerticalStrut(8));
+               ChangeListener optionsChangedListener = new ChangeListener() {
+                       public void optionsChanged() {
+                               enableOkButton();
+                       }
+               };
+               // distance limits
+               JPanel distLimitPanel = new JPanel();
+               distLimitPanel.setLayout(new FlowLayout());
+               _distLimitRadio.setSelected(true);
+               _distLimitRadio.addItemListener(optionsChangedListener);
+               distLimitPanel.add(_distLimitRadio);
+               _distanceField = new WholeNumberField(3);
+               _distanceField.addKeyListener(optionsChangedListener);
+               distLimitPanel.add(_distanceField);
+               String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
+                       I18nManager.getText("units.miles")};
+               _distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
+               _distUnitsDropdown.addItemListener(optionsChangedListener);
+               distLimitPanel.add(_distUnitsDropdown);
+               distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               limitsPanel.add(distLimitPanel);
+
+               // time limit panel
+               JPanel timeLimitPanel = new JPanel();
+               timeLimitPanel.setLayout(new FlowLayout());
+               _timeLimitRadio.addItemListener(optionsChangedListener);
+               timeLimitPanel.add(_timeLimitRadio);
+               _limitHourField = new WholeNumberField(2);
+               _limitHourField.addKeyListener(optionsChangedListener);
+               timeLimitPanel.add(_limitHourField);
+               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
+               _limitMinField = new WholeNumberField(3);
+               _limitMinField.addKeyListener(optionsChangedListener);
+               timeLimitPanel.add(_limitMinField);
+               timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
+               timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               limitsPanel.add(timeLimitPanel);
+
+               dialogPanel.add(limitsPanel, BorderLayout.NORTH);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               // OK button
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               performFunction();
+                       }
+               });
+               buttonPanel.add(_okButton);
+               // Cancel button
+               _cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               _cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               _cancelButton.addKeyListener(new KeyAdapter() {
+                       public void keyPressed(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+               });
+               buttonPanel.add(_cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+       /**
+        * Enable or disable the OK button according to the inputs
+        */
+       private void enableOkButton()
+       {
+               boolean enabled = false;
+               if (_distLimitRadio.isSelected()) {
+                       enabled = _distanceField.getValue() > 0;
+               }
+               else if (_timeLimitRadio.isSelected()) {
+                       enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
+               }
+               _okButton.setEnabled(enabled);
+
+               // Also enable/disable the other fields
+               _distanceField.setEnabled(_distLimitRadio.isSelected());
+               _distUnitsDropdown.setEnabled(_distLimitRadio.isSelected());
+               _limitHourField.setEnabled(_timeLimitRadio.isSelected());
+               _limitMinField.setEnabled(_timeLimitRadio.isSelected());
+       }
+
+       /**
+        * @return selected time limit in seconds, or 0
+        */
+       protected int getTimeLimitInSeconds()
+       {
+               if (_timeLimitRadio.isSelected()
+                       && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0))
+               {
+                       int timeLimitSeconds = _limitHourField.getValue() * 60 * 60
+                               + _limitMinField.getValue() * 60;
+                       if (timeLimitSeconds > 0)
+                               return timeLimitSeconds;
+               }
+               return 0;
+       }
+
+       /**
+        * @return selected distance limit in radians, or 0.0
+        */
+       protected double getDistanceLimitRadians()
+       {
+               if (_distLimitRadio.isSelected()
+                       && _distanceField.getValue() > 0)
+               {
+                       final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES,
+                               UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
+                       Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()];
+                       double distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
+                       return distLimitRadians;
+               }
+               return 0.0;
+       }
+
+       /**
+        * The dialog has been completed and OK pressed, so do the corresponding function
+        */
+       protected abstract void performFunction();
+
+       /**
+        * Create a description for the given multiple of the configured limit
+        * @param inMultiple multiple of selected limit
+        * @return language-specific String description for waypoint names
+        */
+       protected String createLimitDescription(int inMultiple)
+       {
+               if (_distLimitRadio.isSelected()
+                       && _distanceField.getValue() > 0)
+               {
+                       final int distLimit = inMultiple * _distanceField.getValue();
+                       final String[] distUnits = {"kilometres", "metres", "miles"};
+                       final String unitKey = "units." + distUnits[_distUnitsDropdown.getSelectedIndex()] + ".short";
+                       return "" + distLimit + " " + I18nManager.getText(unitKey);
+               }
+               else if (_timeLimitRadio.isSelected())
+               {
+                       final long timeLimitSecs = (long) getTimeLimitInSeconds() * inMultiple;
+                       return new TimeDifference(timeLimitSecs).getDescription();
+               }
+               return null;
+       }
+}
index 76c69da7f928aa3099e383c5603a06de11997025..b3ee1b292447bd02e4c3eaab1b0f5529258eb85d 100644 (file)
@@ -243,9 +243,10 @@ public class DownloadOsmFunction extends GenericFunction implements Runnable
         */
        public void run()
        {
-               final String url = "http://www.overpass-api.de/api/xapi?map?bbox=" +
-                       _latLonLabels[1].getText() + "," + _latLonLabels[3].getText() + "," +
-                       _latLonLabels[2].getText() + "," + _latLonLabels[0].getText();
+               String url = "http://overpass-api.de/api/interpreter?data=(node(" +
+                       _latLonLabels[3].getText() + "," + _latLonLabels[1].getText() + "," +
+                       _latLonLabels[0].getText() + "," + _latLonLabels[2].getText() + ");<;);out%20qt;";
+               // System.out.println(url);
 
                byte[] buffer = new byte[1024];
                InputStream inStream = null;
index d32828ab997bb34fe801a515fd86da4744ce2893..f3b7aa5a937f901130e6226de63a6240472ae93a 100644 (file)
@@ -6,7 +6,7 @@ import tim.prune.threedee.ImageDefinition;
 import tim.prune.threedee.TerrainDefinition;
 
 /**
- * Abstract superclass for pov and svg export functions
+ * Abstract superclass of any 3d export function, currently only the PovExporter
  */
 public abstract class Export3dFunction extends GenericFunction
 {
index 25fb8ec42bae058dfb29cb4df2fe2f28646318f0..dc1e059f86e36445bb1bea0bdbdc2d2b6ac47884 100644 (file)
@@ -41,7 +41,7 @@ public class HelpScreen extends GenericFunction
                        == JOptionPane.YES_OPTION)
                {
                        // User selected to launch home page
-                       BrowserLauncher.launchBrowser("http://activityworkshop.net/software/gpsprune/index.html");
+                       BrowserLauncher.launchBrowser("https://activityworkshop.net/software/gpsprune/index.html");
                }
        }
 }
diff --git a/tim/prune/function/SearchOsmPoisFunction.java b/tim/prune/function/SearchOsmPoisFunction.java
new file mode 100644 (file)
index 0000000..a35c7b3
--- /dev/null
@@ -0,0 +1,179 @@
+package tim.prune.function;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import tim.prune.App;
+import tim.prune.I18nManager;
+import tim.prune.data.Coordinate;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Distance;
+import tim.prune.data.Field;
+import tim.prune.data.Latitude;
+import tim.prune.data.Longitude;
+import tim.prune.function.search.GenericDownloaderFunction;
+import tim.prune.function.search.SearchResult;
+
+/**
+ * Function to load nearby point information from OSM
+ */
+public class SearchOsmPoisFunction extends GenericDownloaderFunction
+{
+       /** Maximum distance from point in m */
+       private static final int MAX_DISTANCE = 250;
+       /** Coordinates to search for */
+       private double _searchLatitude = 0.0, _searchLongitude = 0.0;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public SearchOsmPoisFunction(App inApp) {
+               super(inApp);
+       }
+
+       /**
+        * @return name key
+        */
+       public String getNameKey() {
+               return "function.searchosmpois";
+       }
+
+       /**
+        * @param inColNum index of column, 0 or 1
+        * @return key for this column
+        */
+       protected String getColumnKey(int inColNum)
+       {
+               if (inColNum == 0) return "dialog.osmpois.column.name";
+               return "dialog.osmpois.column.type";
+       }
+
+
+       /**
+        * Run method to get the nearby points in a separate thread
+        */
+       public void run()
+       {
+               _statusLabel.setText(I18nManager.getText("confirm.running"));
+               // Get coordinates from current point (if any) or from centre of screen
+               DataPoint point = _app.getTrackInfo().getCurrentPoint();
+               if (point == null)
+               {
+                       double[] coords  = _app.getViewport().getBounds();
+                       _searchLatitude  = (coords[0] + coords[2]) / 2.0;
+                       _searchLongitude = (coords[1] + coords[3]) / 2.0;
+               }
+               else
+               {
+                       _searchLatitude  = point.getLatitude().getDouble();
+                       _searchLongitude = point.getLongitude().getDouble();
+               }
+
+               // Submit search (language not an issue here)
+               submitSearch(_searchLatitude, _searchLongitude);
+
+               // Set status label according to error or "none found", leave blank if ok
+               if (_errorMessage == null && _trackListModel.isEmpty()) {
+                       _errorMessage = I18nManager.getText("dialog.osmpois.nonefound");
+               }
+               _statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
+       }
+
+       /**
+        * Submit the search for the given parameters
+        * @param inLat latitude
+        * @param inLon longitude
+        */
+       private void submitSearch(double inLat, double inLon)
+       {
+               String coords = "around:" + MAX_DISTANCE + "," + inLat + "," + inLon;
+               String urlString = "http://overpass-api.de/api/interpreter?data=("
+                       + "node(" + coords + ")[\"amenity\"][\"name\"];"
+                       + "node(" + coords + ")[\"railway\"][\"name\"];"
+                       + "node(" + coords + ")[\"highway\"][\"name\"];"
+                       + ");out%20qt;";
+               //System.out.println(urlString);
+               // Parse the returned XML with a special handler
+               SearchOsmPoisXmlHandler xmlHandler = new SearchOsmPoisXmlHandler();
+               InputStream inStream = null;
+
+               try
+               {
+                       URL url = new URL(urlString);
+                       SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+                       inStream = url.openStream();
+                       saxParser.parse(inStream, xmlHandler);
+               }
+               catch (Exception e) {
+                       _errorMessage = e.getClass().getName() + " - " + e.getMessage();
+               }
+               // Close stream and ignore errors
+               try {
+                       inStream.close();
+               } catch (Exception e) {}
+
+               // Calculate distances for each returned point
+               DataPoint searchPoint = new DataPoint(new Latitude(_searchLatitude, Coordinate.FORMAT_DECIMAL_FORCE_POINT),
+                       new Longitude(_searchLongitude, Coordinate.FORMAT_DECIMAL_FORCE_POINT), null);
+               for (SearchResult result : xmlHandler.getPointList())
+               {
+                       Latitude pointLat = new Latitude(result.getLatitude());
+                       Longitude pointLon = new Longitude(result.getLongitude());
+                       DataPoint foundPoint = new DataPoint(pointLat, pointLon, null);
+                       double dist = DataPoint.calculateRadiansBetween(searchPoint, foundPoint);
+                       result.setLength(Distance.convertRadiansToDistance(dist));
+               }
+
+               // TODO: maybe limit number of results using MAX_RESULTS
+               // Add track list to model
+               ArrayList<SearchResult> pointList = xmlHandler.getPointList();
+               _trackListModel.addTracks(pointList, true);
+               _trackListModel.setShowPointTypes(true);
+
+               // Show error message if any
+               if (_trackListModel.isEmpty())
+               {
+                       String error = xmlHandler.getErrorMessage();
+                       if (error != null && !error.equals(""))
+                       {
+                               _app.showErrorMessageNoLookup(getNameKey(), error);
+                               _errorMessage = error;
+                       }
+               }
+       }
+
+       /**
+        * Load the selected point(s)
+        */
+       protected void loadSelected()
+       {
+               // Find the rows selected in the table and get the corresponding coords
+               int numSelected = _trackTable.getSelectedRowCount();
+               if (numSelected < 1) return;
+               int[] rowNums = _trackTable.getSelectedRows();
+               for (int i=0; i<numSelected; i++)
+               {
+                       int rowNum = rowNums[i];
+                       if (rowNum >= 0 && rowNum < _trackListModel.getRowCount())
+                       {
+                               String lat = _trackListModel.getTrack(rowNum).getLatitude();
+                               String lon = _trackListModel.getTrack(rowNum).getLongitude();
+                               if (lat != null && lon != null)
+                               {
+                                       DataPoint point = new DataPoint(new Latitude(lat), new Longitude(lon), null);
+                                       point.setFieldValue(Field.WAYPT_NAME, _trackListModel.getTrack(rowNum).getTrackName(), false);
+                                       _app.createPoint(point);
+                               }
+                       }
+               }
+               // Close the dialog
+               _cancelled = true;
+               _dialog.dispose();
+       }
+}
diff --git a/tim/prune/function/SearchOsmPoisXmlHandler.java b/tim/prune/function/SearchOsmPoisXmlHandler.java
new file mode 100644 (file)
index 0000000..5683ece
--- /dev/null
@@ -0,0 +1,92 @@
+package tim.prune.function;
+
+import java.util.ArrayList;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import tim.prune.function.search.SearchResult;
+
+/**
+ * XML handler for dealing with XML returned from the OSM Overpass api,
+ * specially for the OSM Poi service
+ */
+public class SearchOsmPoisXmlHandler extends DefaultHandler
+{
+       private ArrayList<SearchResult> _pointList = null;
+       private SearchResult _currPoint = null;
+       private String _errorMessage = null;
+
+
+       /**
+        * React to the start of an XML tag
+        */
+       public void startElement(String inUri, String inLocalName, String inTagName,
+               Attributes inAttributes) throws SAXException
+       {
+               if (inTagName.equals("osm")) {
+                       _pointList = new ArrayList<SearchResult>();
+               }
+               else if (inTagName.equals("node"))
+               {
+                       _currPoint = new SearchResult();
+                       _currPoint.setLatitude(inAttributes.getValue("lat"));
+                       _currPoint.setLongitude(inAttributes.getValue("lon"));
+               }
+               else if (inTagName.equals("tag") && _currPoint != null) {
+                       processTag(inAttributes);
+               }
+               super.startElement(inUri, inLocalName, inTagName, inAttributes);
+       }
+
+       /**
+        * @param inAttributes attributes to process
+        */
+       private void processTag(Attributes inAttributes)
+       {
+               String key = inAttributes.getValue("k");
+               if (key != null)
+               {
+                       String value = inAttributes.getValue("v");
+                       if (key.equals("name"))
+                       {
+                               _currPoint.setTrackName(value);
+                       }
+                       else if (key.equals("amenity") || key.equals("highway") || key.equals("railway"))
+                       {
+                               _currPoint.setPointType(value);
+                       }
+               }
+       }
+
+       /**
+        * React to the end of an XML tag
+        */
+       public void endElement(String inUri, String inLocalName, String inTagName)
+       throws SAXException
+       {
+               if (inTagName.equals("node"))
+               {
+                       // end of the entry
+                       if (_currPoint.getTrackName() != null && !_currPoint.getTrackName().equals(""))
+                       _pointList.add(_currPoint);
+               }
+               super.endElement(inUri, inLocalName, inTagName);
+       }
+
+       /**
+        * @return the list of points
+        */
+       public ArrayList<SearchResult> getPointList()
+       {
+               return _pointList;
+       }
+
+       /**
+        * @return error message, if any
+        */
+       public String getErrorMessage() {
+               return _errorMessage;
+       }
+}
diff --git a/tim/prune/function/SelectTimezoneFunction.java b/tim/prune/function/SelectTimezoneFunction.java
new file mode 100644 (file)
index 0000000..8f5f778
--- /dev/null
@@ -0,0 +1,640 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+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 java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+import tim.prune.gui.CombinedListAndModel;
+import tim.prune.gui.GuiGridLayout;
+
+/**
+ * Class to provide the gui for selecting an alternative timezone
+ */
+public class SelectTimezoneFunction extends GenericFunction
+{
+       /** Arraylist of timezone infos */
+       private ArrayList<TimezoneDetails> _zoneInfo;
+       /** Dialog */
+       private JDialog _dialog = null;
+       /** Radio button to select system timezone instead of using listboxes */
+       private JRadioButton _systemRadio = null;
+       /** Radio button to select timezone using listboxes */
+       private JRadioButton _customRadio = null;
+       /** Array of list boxes */
+       private CombinedListAndModel[] _listBoxes = null;
+       /** Label for selected zone */
+       private JLabel _selectedZoneLabel = null;
+       /** Label for offset of selected zone */
+       private JLabel _selectedOffsetLabel = null;
+       /** OK button for finishing */
+       private JButton _okButton = null;
+
+       private static final int LIST_REGIONS = 0;
+       private static final int LIST_OFFSETS = 1;
+       private static final int LIST_GROUPS  = 2;
+       private static final int LIST_NAMES   = 3;
+
+       /**
+        * Inner class for listening to list clicks
+        */
+       class ListListener implements ListSelectionListener
+       {
+               private int _key = 0;
+               /** Constructor */
+               ListListener(int inKey) {_key = inKey;}
+               /** Listen for selection changes */
+               public void valueChanged(ListSelectionEvent inEvent) {
+                       if (!inEvent.getValueIsAdjusting()) {
+                               processListClick(_key);
+                       }
+               }
+       }
+
+       /** Inner class to hold categorisation info for a timezone */
+       class TimezoneDetails
+       {
+               public String _id;
+               public String _region;
+               public int    _offset;
+               public String _group;
+               public String _name;
+       }
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public SelectTimezoneFunction(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.selecttimezone";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               collectTimezoneInfo();
+               _systemRadio.setText(I18nManager.getText("dialog.settimezone.system") + " ("
+                       + TimeZone.getDefault().getID() + ")");
+               // Set up dialog according to current config
+               String selectedTimezone = Config.getConfigString(Config.KEY_TIMEZONE_ID);
+               if (selectedTimezone == null || selectedTimezone.equals(""))
+               {
+                       _systemRadio.setSelected(true);
+               }
+               else
+               {
+                       _customRadio.setSelected(true);
+               }
+               _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));
+               // Listener for radio buttons
+               ActionListener radioListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent inEvent) {
+                               radioSelected(_systemRadio.isSelected());
+                       }
+               };
+               FocusListener radioFocusListener = new FocusAdapter() {
+                       public void focusGained(FocusEvent inEvent) {
+                               radioSelected(_systemRadio.isSelected());
+                       }
+               };
+
+               // Panel at top
+               JPanel topPanel = new JPanel();
+               topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
+               JLabel topLabel = new JLabel(I18nManager.getText("dialog.settimezone.intro"));
+               topLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+               topPanel.add(topLabel);
+               _systemRadio = new JRadioButton(I18nManager.getText("dialog.settimezone.system"));
+               _systemRadio.addActionListener(radioListener);
+               _systemRadio.addFocusListener(radioFocusListener);
+               topPanel.add(_systemRadio);
+               _customRadio = new JRadioButton(I18nManager.getText("dialog.settimezone.custom"));
+               _customRadio.addActionListener(radioListener);
+               _customRadio.addFocusListener(radioFocusListener);
+               topPanel.add(_customRadio);
+               ButtonGroup radioGroup = new ButtonGroup();
+               radioGroup.add(_systemRadio); radioGroup.add(_customRadio);
+               dialogPanel.add(topPanel, BorderLayout.NORTH);
+
+               // Main panel with box layout, list Panel with four lists in a grid
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+
+               JPanel listsPanel = new JPanel();
+               listsPanel.setLayout(new GridLayout(1, 4));
+               _listBoxes = new CombinedListAndModel[4];
+               // First list for regions
+               _listBoxes[LIST_REGIONS] = new CombinedListAndModel(0);
+               // Add listener for list selection changes
+               _listBoxes[LIST_REGIONS].addListSelectionListener(new ListListener(LIST_REGIONS));
+               JScrollPane scrollPane = new JScrollPane(_listBoxes[LIST_REGIONS]);
+               scrollPane.setPreferredSize(new Dimension(100, 200));
+               scrollPane.setMinimumSize(new Dimension(100, 200));
+               scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+               listsPanel.add(scrollPane);
+
+               // second list for offsets
+               _listBoxes[LIST_OFFSETS] = new CombinedListAndModel(1);
+               _listBoxes[LIST_OFFSETS].setMaxNumEntries(24);
+               _listBoxes[LIST_OFFSETS].addListSelectionListener(new ListListener(LIST_OFFSETS));
+               scrollPane = new JScrollPane(_listBoxes[LIST_OFFSETS]);
+               scrollPane.setPreferredSize(new Dimension(100, 200));
+               scrollPane.setMinimumSize(new Dimension(100, 200));
+               scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+               listsPanel.add(scrollPane);
+
+               // third list for groups
+               _listBoxes[LIST_GROUPS] = new CombinedListAndModel(2);
+               _listBoxes[LIST_GROUPS].setMaxNumEntries(20);
+               _listBoxes[LIST_GROUPS].addListSelectionListener(new ListListener(LIST_GROUPS));
+               scrollPane = new JScrollPane(_listBoxes[LIST_GROUPS]);
+               scrollPane.setPreferredSize(new Dimension(100, 200));
+               scrollPane.setMinimumSize(new Dimension(100, 200));
+               scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+               listsPanel.add(scrollPane);
+
+               // fourth list for names
+               _listBoxes[LIST_NAMES] = new CombinedListAndModel(3);
+               _listBoxes[LIST_NAMES].setMaxNumEntries(20);
+               _listBoxes[LIST_NAMES].addListSelectionListener(new ListListener(LIST_NAMES));
+               scrollPane = new JScrollPane(_listBoxes[LIST_NAMES]);
+               scrollPane.setPreferredSize(new Dimension(100, 200));
+               scrollPane.setMinimumSize(new Dimension(100, 200));
+               scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+               listsPanel.add(scrollPane);
+               mainPanel.add(listsPanel);
+
+               // Details labels underneath lists - description and offset
+               JPanel detailsPanel = new JPanel();
+               GuiGridLayout grid = new GuiGridLayout(detailsPanel);
+               grid.add(new JLabel(I18nManager.getText("dialog.settimezone.selectedzone") + " :"));
+               _selectedZoneLabel = new JLabel("");
+               grid.add(_selectedZoneLabel);
+               grid.add(new JLabel(I18nManager.getText("dialog.settimezone.offsetfromutc") + " :"));
+               _selectedOffsetLabel = new JLabel("");
+               grid.add(_selectedOffsetLabel);
+               mainPanel.add(detailsPanel);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+
+               // close window if escape pressed
+               KeyAdapter escListener = new KeyAdapter() {
+                       public void keyReleased(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               _listBoxes[LIST_REGIONS].addKeyListener(escListener);
+               _listBoxes[LIST_OFFSETS].addKeyListener(escListener);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               // OK button
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               finishSelectTimezone();
+                       }
+               });
+               buttonPanel.add(_okButton);
+               _okButton.addKeyListener(escListener);
+               // Cancel button
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               cancelButton.addKeyListener(new KeyAdapter() {
+                       public void keyPressed(KeyEvent inE) {
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return dialogPanel;
+       }
+
+       /**
+        * React to changes in the radio buttons
+        * @param inUseSystem true for system, false for custom
+        */
+       private void radioSelected(boolean inUseSystem)
+       {
+               for (int i=0; i<_listBoxes.length; i++)
+               {
+                       if (inUseSystem)
+                       {
+                               _listBoxes[i].clear();
+                       }
+                       _listBoxes[i].setEnabled(!inUseSystem);
+               }
+               if (!inUseSystem)
+               {
+                       populateTimezoneRegions();
+                       populateTimezoneOffsets(null);
+                       preselectTimezone(Config.getConfigString(Config.KEY_TIMEZONE_ID));
+               }
+               showTimezoneDetails();
+       }
+
+       /**
+        * React to a selection change on one of our lists
+        * @param inKey key of list which was clicked
+        */
+       private void processListClick(int inKey)
+       {
+               final boolean offsetSelected = _listBoxes[LIST_OFFSETS].getSelectedItem() != null;
+               final boolean groupSelected = _listBoxes[LIST_GROUPS].getSelectedItem() != null;
+               // Update offsets?
+               if (inKey == LIST_REGIONS)
+               {
+                       populateTimezoneOffsets(_listBoxes[LIST_REGIONS].getSelectedItem());
+               }
+               // Update groups?
+               if (inKey == LIST_OFFSETS
+                       || (inKey == LIST_REGIONS && !offsetSelected))
+               {
+                       populateTimezoneGroups(_listBoxes[LIST_REGIONS].getSelectedItem(), _listBoxes[LIST_OFFSETS].getSelectedItem());
+               }
+               // Update names?
+               if (inKey == LIST_GROUPS
+                       || (inKey <= LIST_OFFSETS && !groupSelected))
+               {
+                       populateTimezoneNames(_listBoxes[LIST_REGIONS].getSelectedItem(), _listBoxes[LIST_OFFSETS].getSelectedItem(),
+                               _listBoxes[LIST_GROUPS].getSelectedItem());
+               }
+               // Show the details of the selected timezone
+               showTimezoneDetails();
+       }
+
+       /**
+        * Use the system information to populate the list of available timezones
+        */
+       private void collectTimezoneInfo()
+       {
+               _zoneInfo = new ArrayList<TimezoneDetails>();
+               for (String id : TimeZone.getAvailableIDs())
+               {
+                       String region = getRegion(id);
+                       if (region != null)
+                       {
+                               TimeZone tz = TimeZone.getTimeZone(id);
+                               TimezoneDetails details = new TimezoneDetails();
+                               details._id = id;
+                               details._region = region;
+                               details._offset = tz.getOffset(System.currentTimeMillis()) / 1000 / 60;
+                               details._group = tz.getDisplayName();
+                               details._name = getNameWithoutRegion(id);
+                               _zoneInfo.add(details);
+                       }
+               }
+       }
+
+       /**
+        * Populate the timezone regions into the region list
+        */
+       private void populateTimezoneRegions()
+       {
+               _listBoxes[LIST_REGIONS].clear();
+               TreeSet<String> regions = new TreeSet<String>();
+               for (TimezoneDetails currZone : _zoneInfo)
+               {
+                       regions.add(currZone._region);
+               }
+               for (String region : regions)
+               {
+                       _listBoxes[LIST_REGIONS].addItem(region);
+               }
+       }
+
+       /**
+        * Extract the timezone region from the id
+        */
+       private static String getRegion(String inId)
+       {
+               final int slashPos = (inId == null ? -1 : inId.indexOf('/'));
+               if (slashPos > 0)
+               {
+                       return inId.substring(0, slashPos);
+               }
+               return null;
+       }
+
+       /**
+        * Populate the second listbox with the offsets for the given region
+        * @param inRegion selected region, or null if none selected
+        */
+       private void populateTimezoneOffsets(String inRegion)
+       {
+               _listBoxes[LIST_OFFSETS].clear();
+               TreeSet<Integer> offsetsinMinutes = new TreeSet<Integer>();
+               for (TimezoneDetails currZone : _zoneInfo)
+               {
+                       String region = currZone._region;
+                       if (inRegion == null || region.equals(inRegion))
+                       {
+                               offsetsinMinutes.add(currZone._offset);
+                       }
+               }
+               for (Integer offset : offsetsinMinutes)
+               {
+                       _listBoxes[LIST_OFFSETS].addItem(makeOffsetString(offset));
+               }
+       }
+
+       /**
+        * @return String containing offset for display
+        */
+       private static String makeOffsetString(int inOffsetInMinutes)
+       {
+               if (inOffsetInMinutes == 0) return "0";
+               final boolean isWholeHours = (inOffsetInMinutes % 60) == 0;
+               if (isWholeHours)
+               {
+                       return (inOffsetInMinutes > 0 ? "+" : "") + (inOffsetInMinutes / 60);
+               }
+               final double numHours = inOffsetInMinutes / 60.0;
+               return (inOffsetInMinutes > 0 ? "+" : "") + numHours;
+       }
+
+       /**
+        * Populate the group list using the specified region and offset
+        * @param inRegion selected region (if any) from the first list
+        * @param inOffset selected offset (if any) from the second list
+        */
+       private void populateTimezoneGroups(String inRegion, String inOffset)
+       {
+               _listBoxes[LIST_GROUPS].clear();
+               // Convert given offset string (in hours) into numeric offset (in minutes)
+               final int offsetMins = convertToMinutes(inOffset);
+
+               TreeSet<String> zoneGroups = new TreeSet<String>();
+               for (TimezoneDetails currZone : _zoneInfo)
+               {
+                       if (inRegion == null || currZone._region.equals(inRegion))
+                       {
+                               if (offsetMins == -1 || offsetMins == currZone._offset)
+                               {
+                                       zoneGroups.add(currZone._group);
+                               }
+                       }
+               }
+               // If the region and offset were given, then list is unlimited
+               _listBoxes[LIST_GROUPS].setUnlimited(inRegion != null && inOffset != null);
+               // Add all the found names to the listbox
+               for (String group : zoneGroups)
+               {
+                       _listBoxes[LIST_GROUPS].addItem(group);
+               }
+       }
+
+       /**
+        * Populate the group list using the specified region, offset and group
+        * @param inRegion selected region (if any) from the first list
+        * @param inOffset selected offset (if any) from the second list
+        * @param inGroup selected group (if any) from the third list
+        */
+       private void populateTimezoneNames(String inRegion, String inOffset, String inGroup)
+       {
+               CombinedListAndModel nameList = _listBoxes[LIST_NAMES];
+               nameList.clear();
+               // Convert given offset string (in hours) into numeric offset (in minutes)
+               final int offsetMins = convertToMinutes(inOffset);
+
+               TreeSet<String> zoneNames = new TreeSet<String>();
+               for (TimezoneDetails currZone : _zoneInfo)
+               {
+                       if ((inRegion == null || currZone._region.equals(inRegion))
+                               && (offsetMins == -1 || currZone._offset == offsetMins)
+                               && (inGroup == null || currZone._group.equals(inGroup)))
+                       {
+                               zoneNames.add(currZone._name);
+                       }
+               }
+               // If the region and offset were given, then list is unlimited
+               nameList.setUnlimited(inRegion != null && inOffset != null && inRegion != null);
+               // Add all the found names to the listbox
+               for (String name : zoneNames)
+               {
+                       nameList.addItem(name);
+               }
+       }
+
+       /**
+        * Convert the given String from hours to minutes
+        * @param inOffsetInHours String from listbox in +/- hours
+        * @return offset in minutes, or -1
+        */
+       private static int convertToMinutes(String inOffsetInHours)
+       {
+               int offsetMins = -1;
+               try {
+                       offsetMins = (int) (60 * Double.parseDouble(inOffsetInHours));
+               }
+               catch (NumberFormatException nfe) {} // offset stays -1
+               catch (NullPointerException npe) {} // offset stays -1
+               return offsetMins;
+       }
+
+       /**
+        * Remove the timezone region from the id to just leave the name after the slash
+        */
+       private static String getNameWithoutRegion(String inId)
+       {
+               final int slashPos = (inId == null ? -1 : inId.indexOf('/'));
+               if (slashPos > 0)
+               {
+                       return inId.substring(slashPos + 1);
+               }
+               return null;
+       }
+
+       /**
+        * Get the selected timezone, or null if none selected
+        */
+       private TimeZone getSelectedTimezone()
+       {
+               if (_systemRadio.isSelected())
+               {
+                       return TimeZone.getDefault();
+               }
+
+               String chosenRegion = _listBoxes[LIST_REGIONS].getSelectedItem();
+               // Convert given offset string (in hours) into numeric offset (in minutes)
+               final int offsetMins = convertToMinutes(_listBoxes[LIST_OFFSETS].getSelectedItem());
+               String chosenGroup = _listBoxes[LIST_GROUPS].getSelectedItem();
+               String chosenName = _listBoxes[LIST_NAMES].getSelectedItem();
+
+               TreeSet<String> zoneIds = new TreeSet<String>();
+               for (TimezoneDetails currZone : _zoneInfo)
+               {
+                       if ((chosenRegion == null || currZone._region.equals(chosenRegion))
+                               && (offsetMins == -1 || currZone._offset == offsetMins)
+                               && (chosenGroup == null || currZone._group.equals(chosenGroup))
+                               && (chosenName == null || currZone._name.equals(chosenName)))
+                       {
+                               zoneIds.add(currZone._id);
+                               if (zoneIds.size() > 1) {
+                                       break;  // exit loop now, we've got too many
+                               }
+                       }
+               }
+               // Should have exactly one result now
+               if (zoneIds.size() == 1)
+               {
+                       return TimeZone.getTimeZone(zoneIds.first());
+               }
+
+               // none selected (yet)
+               return null;
+       }
+
+       /**
+        * Show the details of the selected timezone
+        */
+       private void showTimezoneDetails()
+       {
+               TimeZone selectedTimezone = getSelectedTimezone();
+               if (selectedTimezone == null)
+               {
+                       // Clear details labels
+                       _selectedZoneLabel.setText("");
+                       _selectedOffsetLabel.setText("");
+               }
+               else
+               {
+                       // Fill results in labels
+                       String desc = selectedTimezone.getID() + " - " + selectedTimezone.getDisplayName();
+                       _selectedZoneLabel.setText(desc);
+                       String offsets = getOffsetDescription(selectedTimezone);
+                       _selectedOffsetLabel.setText(offsets);
+               }
+               _okButton.setEnabled(selectedTimezone != null);
+       }
+
+       /**
+        * @param inTimezone selected timezone
+        * @return String describing the time offset(s) of this zone including winter/summer time
+        */
+       private static String getOffsetDescription(TimeZone inTimezone)
+       {
+               if (inTimezone == null)
+               {
+                       return "";
+               }
+               TreeSet<Integer> offsetsinMinutes = new TreeSet<Integer>();
+               long testTimeMillis = System.currentTimeMillis();
+               final long testPeriodInMillis = 1000L * 60 * 60 * 24 * 30 * 2;
+               for (int i=0; i<5; i++)
+               {
+                       offsetsinMinutes.add(inTimezone.getOffset(testTimeMillis) / 1000 / 60);
+                       testTimeMillis += testPeriodInMillis;
+               }
+               // Make String describing the sorted set
+               StringBuffer buff = new StringBuffer();
+               for (Integer offset : offsetsinMinutes)
+               {
+                       if (buff.length() > 0)
+                       {
+                               buff.append(" / ");
+                       }
+                       buff.append(makeOffsetString(offset));
+               }
+               return buff.toString();
+       }
+
+       /**
+        * On entry to the dialog, select the items in each listbox
+        * according to the given preselected timezone id
+        * @param zoneId id of zone to select
+        */
+       private void preselectTimezone(String zoneId)
+       {
+               TimeZone tz = (zoneId == null ? TimeZone.getDefault() : TimeZone.getTimeZone(zoneId));
+               if (tz != null)
+               {
+                       _listBoxes[LIST_REGIONS].selectItem(getRegion(zoneId));
+                       _listBoxes[LIST_OFFSETS].selectItem(makeOffsetString(tz.getOffset(System.currentTimeMillis()) / 1000 / 60));
+                       _listBoxes[LIST_GROUPS].selectItem(tz.getDisplayName());
+                       _listBoxes[LIST_NAMES].selectItem(getNameWithoutRegion(zoneId));
+               }
+       }
+
+       /**
+        * Finish the dialog by setting the config according to the selected zone
+        */
+       private void finishSelectTimezone()
+       {
+               TimeZone selectedTimezone = getSelectedTimezone();
+               if (_systemRadio.isSelected() || selectedTimezone == null)
+               {
+                       // Clear config, use default system timezone instead
+                       Config.setConfigString(Config.KEY_TIMEZONE_ID, null);
+               }
+               else
+               {
+                       // Get selected timezone, set in config
+                       Config.setConfigString(Config.KEY_TIMEZONE_ID, selectedTimezone.getID());
+               }
+               _dialog.dispose();
+               // Make sure listeners know to update themselves
+               UpdateMessageBroker.informSubscribers(DataSubscriber.UNITS_CHANGED);
+       }
+}
diff --git a/tim/prune/function/SetLineWidth.java b/tim/prune/function/SetLineWidth.java
deleted file mode 100644 (file)
index feb49c2..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package tim.prune.function;
-
-import tim.prune.App;
-import tim.prune.DataSubscriber;
-import tim.prune.UpdateMessageBroker;
-import tim.prune.config.Config;
-
-/**
- * Function to set the width with which lines are drawn
- */
-public class SetLineWidth extends SingleNumericParameterFunction
-{
-
-       /**
-        * Constructor
-        * @param inApp App object
-        */
-       public SetLineWidth(App inApp) {
-               super(inApp, 1, 4);
-       }
-
-       /** @return name key */
-       public String getNameKey() {
-               return "function.setlinewidth";
-       }
-
-       /** @return description key */
-       public String getDescriptionKey() {
-               return "dialog.setlinewidth.text";
-       }
-
-       /** @return the current value to display */
-       public int getCurrentParamValue() {
-               return Config.getConfigInt(Config.KEY_LINE_WIDTH);
-       }
-
-       /**
-        * Run function
-        */
-       public void begin()
-       {
-               // Not required, because this function is started from a ChooseSingleParameter function
-               // and goes directly to the completeFunction method.
-       }
-
-       /**
-        * Complete the function using the given line width parameter
-        */
-       public void completeFunction(int inLineWidth)
-       {
-               final int currLineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
-               if (inLineWidth >= 1 && inLineWidth <= 4 && inLineWidth != currLineWidth)
-               {
-                       Config.setConfigInt(Config.KEY_LINE_WIDTH, inLineWidth);
-                       UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
-               }
-       }
-}
index faca5a276f685218919d0d978ca770d84d4bcdb0..7c923d3a698d3ad9f1ead4df4c5b59f4f4529304 100644 (file)
@@ -31,7 +31,6 @@ public abstract class UrlGenerator
                MAP_SOURCE_BING,       /* Bing */
                MAP_SOURCE_PEAKFINDER, /* PeakFinder */
                MAP_SOURCE_GEOHACK,    /* Geohack */
-               MAP_SOURCE_PANORAMIO,  /* Panoramio */
        }
 
        /**
@@ -54,7 +53,6 @@ public abstract class UrlGenerator
                                return generateBingUrl(inTrackInfo);
                        case MAP_SOURCE_PEAKFINDER:
                        case MAP_SOURCE_GEOHACK:
-                       case MAP_SOURCE_PANORAMIO:
                                return generateUrlForPoint(inSource, inTrackInfo);
                        case MAP_SOURCE_OSM:
                        default:
@@ -209,8 +207,6 @@ public abstract class UrlGenerator
                                return generatePeakfinderUrl(currPoint);
                        case MAP_SOURCE_GEOHACK:
                                return generateGeohackUrl(currPoint);
-                       case MAP_SOURCE_PANORAMIO:
-                               return generatePanoramioUrl(currPoint);
                        default:
                                return null;
                }
@@ -240,17 +236,6 @@ public abstract class UrlGenerator
                // TODO: Could use absolute values and S, W but this seems to work
        }
 
-       /**
-        * Generate a url for Panoramio.com
-        * @param inPoint current point, not null
-        * @return URL
-        */
-       private static String generatePanoramioUrl(DataPoint inPoint)
-       {
-               return "http://panoramio.com/map/#lt=" + FIVE_DP.format(inPoint.getLatitude().getDouble())
-                       + "&ln=" + FIVE_DP.format(inPoint.getLongitude().getDouble()) + "&z=1&k=0";
-       }
-
 
        /**
         * Get the median value from the given lat/long range
index 0c2cedb54f5743af654466f8afdb06399916a4e4..3e36d75a448491d5de056a9ddf6833655c85d67b 100644 (file)
@@ -2,6 +2,7 @@ package tim.prune.function.deletebydate;
 
 import java.text.DateFormat;
 import java.util.Date;
+import java.util.TimeZone;
 
 /**
  * Class to hold the information about a date,
@@ -12,7 +13,7 @@ public class DateInfo implements Comparable<DateInfo>
 {
        /** Date, or null for no date - used for earlier/later comparison */
        private Date _date = null;
-       /** String representation of date, for equality comparison */
+       /** String representation of date */
        private String _dateString = null;
        /** Number of points with this date */
        private int _numPoints = 0;
@@ -22,6 +23,15 @@ public class DateInfo implements Comparable<DateInfo>
        // Doesn't really matter what format is used here, as long as dates are different
        private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
 
+
+       /**
+        * @param inZone Timezone to use for the date identification
+        */
+       public static void setTimezone(TimeZone inZone)
+       {
+               DEFAULT_DATE_FORMAT.setTimeZone(inZone);
+       }
+
        /**
         * Constructor
         * @param inDate date object from timestamp
@@ -47,10 +57,10 @@ public class DateInfo implements Comparable<DateInfo>
        }
 
        /**
-        * @return date object, or null
+        * @return string representation of date
         */
-       public Date getDate() {
-               return _date;
+       public String getString() {
+               return _dateString;
        }
 
        /**
index 76286675502bc7403ad3f0085fdc789b1cd6b8f8..979325bb42baa555dd27eb5b92a9ad211fcb05f3 100644 (file)
@@ -24,34 +24,34 @@ public class DateInfoList
         */
        public void addPoint(Date inDate)
        {
+               DateInfo currentInfo = null;
                if (_previousInfo != null && _previousInfo.isSameDate(inDate))
                {
                        // found it
-                       _previousInfo.incrementCount();
+                       currentInfo = _previousInfo;
                }
                else
                {
-                       // loop through list, seeing if date already present
-                       boolean foundDate = false;
+                       // loop through list, to see if date already present
                        for (DateInfo info : _infoList)
                        {
                                if (info.isSameDate(inDate))
                                {
-                                       info.incrementCount();
-                                       _previousInfo = info;
-                                       foundDate = true;
+                                       currentInfo = info;
                                        break;
                                }
                        }
                        // create new info if necessary
-                       if (!foundDate)
+                       if (currentInfo == null)
                        {
-                               _previousInfo = new DateInfo(inDate);
-                               _previousInfo.incrementCount();
-                               _infoList.add(_previousInfo);
+                               currentInfo = new DateInfo(inDate);
+                               _infoList.add(currentInfo);
                                _hasBeenSorted = false;
                        }
+                       _previousInfo = currentInfo;
                }
+               // Now we've identified the current info or created a new one
+               currentInfo.incrementCount();
        }
 
        /**
@@ -64,18 +64,6 @@ public class DateInfoList
                _hasBeenSorted = true;
        }
 
-       /**
-        * not used, can be removed
-        * @return true if any points without dates were found
-        */
-       public boolean hasDatelessPoints()
-       {
-               if (_infoList.isEmpty()) {return false;}
-               sort();
-               DateInfo firstInfo = _infoList.get(0);
-               return (firstInfo != null && firstInfo.isDateless() && firstInfo.getPointCount() > 0);
-       }
-
        /**
         * @return number of entries in the list, including dateless points
         */
index 3085aa3e1f7a220493215345850377ab956ba927..42d52f53b7dda3d75a0c37a7affcf5c47632f0e8 100644 (file)
@@ -22,12 +22,13 @@ import tim.prune.App;
 import tim.prune.DataSubscriber;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.DataPoint;
 import tim.prune.function.compress.MarkAndDeleteFunction;
 
 /**
  * Function to select a date or dates,
- * and delete the corresponding points
+ * and mark the corresponding points for deletion
  */
 public class DeleteByDateFunction extends MarkAndDeleteFunction
 {
@@ -56,6 +57,9 @@ public class DeleteByDateFunction extends MarkAndDeleteFunction
        @Override
        public void begin()
        {
+               // Select the current timezone
+               DateInfo.setTimezone(TimezoneHelper.getSelectedTimezone());
+
                // Make a list of which dates are present in the track
                _infoList.clearAll();
                final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
@@ -65,29 +69,14 @@ public class DeleteByDateFunction extends MarkAndDeleteFunction
                        if (point != null)
                        {
                                if (point.hasTimestamp()) {
-                                       _infoList.addPoint(point.getTimestamp().getCalendar().getTime());
+                                       _infoList.addPoint(point.getTimestamp()
+                                               .getCalendar(TimezoneHelper.getSelectedTimezone()).getTime());
                                }
                                else {
                                        _infoList.addPoint(null); // no timestamp available
                                }
                        }
                }
-//             System.out.println("Debug: info list has dateless points? " + (_infoList.hasDatelessPoints() ? "yes":"no"));
-//             System.out.println("Debug: info list has " + _infoList.getNumEntries() + " different entries");
-//             System.out.println("Debug: info list has " + _infoList.getTotalNumPoints() + " total points");
-//             final boolean checkOk = (_infoList.getTotalNumPoints() == numPoints);
-//             System.out.println("Debug: which " + (checkOk?"IS":"ISN'T!") + " the same as track: " + numPoints);
-
-               // Loop over entries for debug
-//             if (!checkOk)
-//             {
-//                     for (int i=0; i<_infoList.getNumEntries(); i++)
-//                     {
-//                             DateInfo info = _infoList.getDateInfo(i);
-//                             System.out.println("   " + i + " (" + info.getPointCount() + " points) - " +
-//                                     (info.isDateless() ? "no date" : "date"));
-//                     }
-//             }
 
                // Complain if there is only one entry in the list - this means all points are on the same day
                if (_infoList.getNumEntries() < 2)
@@ -182,7 +171,9 @@ public class DeleteByDateFunction extends MarkAndDeleteFunction
                        DataPoint point = _app.getTrackInfo().getTrack().getPoint(p);
                        if (point != null)
                        {
-                               Date date = (point.hasTimestamp() ? point.getTimestamp().getCalendar().getTime() : null);
+                               final Date date = (point.hasTimestamp() ?
+                                       point.getTimestamp().getCalendar(TimezoneHelper.getSelectedTimezone()).getTime()
+                                       : null);
                                boolean pointMarked = false;
                                // Try to match each of the date info objects in the list
                                for (int d=0; d<numDates; d++)
@@ -205,7 +196,8 @@ public class DeleteByDateFunction extends MarkAndDeleteFunction
                if (numMarked > 0) {
                        optionallyDeleteMarkedPoints(numMarked);
                }
-               else {
+               else
+               {
                        // Do nothing   //System.out.println("Nothing selected to delete!");
                        // delete flags might have been reset, so refresh display
                        UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
index 202e01ef8eb8ae9879393895fed285f48697b853..7e5d52ff258160b232cc311a7a565e8c9398e91b 100644 (file)
@@ -1,6 +1,5 @@
 package tim.prune.function.deletebydate;
 
-import java.text.DateFormat;
 import javax.swing.table.AbstractTableModel;
 import tim.prune.I18nManager;
 
@@ -12,8 +11,6 @@ public class DeletionTableModel extends AbstractTableModel
        /** info list, one for each row of table */
        private DateInfoList _infoList = null;
 
-       /** Formatter, determining how dates appear in the table */
-       private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
        /** Column heading for date */
        private static final String COLUMN_HEADING_DATE = I18nManager.getText("fieldname.date");
        /** Column heading for number of points */
@@ -110,7 +107,7 @@ public class DeletionTableModel extends AbstractTableModel
        }
 
        /**
-        * @return cell contents at the given row, column inded
+        * @return cell contents at the given row, column index
         */
        public Object getValueAt(int inRowIndex, int inColIndex)
        {
@@ -124,7 +121,7 @@ public class DeletionTableModel extends AbstractTableModel
                                                if (info.isDateless()) {
                                                        return I18nManager.getText("dialog.deletebydate.nodate");
                                                }
-                                               return DEFAULT_DATE_FORMAT.format(info.getDate());
+                                               return info.getString();
                                        case 1: // number of points
                                                return info.getPointCount();
                                        case 2: // keep
index e8aa2b7a79d8f98f689bbf6b3357c50978f80fb2..199c67e5ef1df774b62834aa13db481ac1aa7a57 100644 (file)
@@ -96,79 +96,6 @@ public class QRDecomposition
                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
index f90bb037327ed9dce75b756fee199ab174230498..c549c59c1a7a59d2f57a05e46067af0e1af6d19d 100644 (file)
@@ -50,7 +50,7 @@ public class GpsiesXmlHandler extends DefaultHandler
                        _track.setDescription(_value);
                }
                else if (inTagName.equals("fileId")) {
-                       _track.setWebUrl("http://gpsies.com/map.do?fileId=" + _value);
+                       _track.setWebUrl("https://gpsies.com/map.do?fileId=" + _value);
                }
                else if (inTagName.equals("trackLengthM")) {
                        try {
index 94ce503e41da2498ce36beb7c2d51dd7d04f1b14..850921303801d33878c6b429ec2709087ed7fd6a 100644 (file)
@@ -12,7 +12,7 @@ import tim.prune.data.Unit;
 import tim.prune.function.search.SearchResult;
 
 /**
- * Model for list of tracks from gpsies.com
+ * Model for list of tracks from a search result (eg gpsies.com, geonames, overpass)
  */
 public class TrackListModel extends AbstractTableModel
 {
@@ -24,6 +24,8 @@ public class TrackListModel extends AbstractTableModel
        private String _lengthColLabel = null;
        /** Number of columns */
        private int _numColumns = 2;
+       /** Normally this model shows distances / lengths, except when this flag is true */
+       private boolean _showPointTypes = false;
        /** Formatter for distances */
        private NumberFormat _distanceFormatter = NumberFormat.getInstance();
 
@@ -75,6 +77,14 @@ public class TrackListModel extends AbstractTableModel
                return _lengthColLabel;
        }
 
+       /**
+        * @param inShowTypes true to show point types, false for distances
+        */
+       public void setShowPointTypes(boolean inShowTypes)
+       {
+               _showPointTypes = inShowTypes;
+       }
+
        /**
         * @param inRowNum row number
         * @param inColNum column number
@@ -83,7 +93,13 @@ public class TrackListModel extends AbstractTableModel
        public Object getValueAt(int inRowNum, int inColNum)
        {
                SearchResult track = _trackList.get(inRowNum);
-               if (inColNum == 0) {return track.getTrackName();}
+               if (inColNum == 0) {
+                       return track.getTrackName();
+               }
+               if (_showPointTypes)
+               {
+                       return track.getPointType();
+               }
                double lengthM = track.getLength();
                // convert to current distance units
                Unit distUnit = Config.getUnitSet().getDistanceUnit();
index 403a0efb69d28b7d8296ea71db65592edf5ccdf7..fa8d4c33d90bf43838f19f039d9b510226fccf51 100644 (file)
@@ -29,7 +29,7 @@ import tim.prune.function.gpsies.TrackListModel;
 
 /**
  * Function to load track information from any source,
- * subclassed for special cases gpsies or wikipedia
+ * subclassed for special cases like gpsies, wikipedia or OSM
  */
 public abstract class GenericDownloaderFunction extends GenericFunction implements Runnable
 {
@@ -116,16 +116,18 @@ public abstract class GenericDownloaderFunction extends GenericFunction implemen
                                if (!e.getValueIsAdjusting())
                                {
                                        final int numSelected = _trackTable.getSelectedRowCount();
+                                       boolean foundUrl = false;
                                        if (numSelected > 0)
                                        {
                                                setDescription(_trackListModel.getTrack(_trackTable.getSelectedRow()).getDescription());
                                                _descriptionBox.setCaretPosition(0);
+                                               foundUrl = _trackListModel.getTrack(_trackTable.getSelectedRow()).getWebUrl() != null;
                                        }
                                        else {
                                                _descriptionBox.setText("");
                                        }
                                        _loadButton.setEnabled(numSelected > 0);
-                                       _showButton.setEnabled(numSelected == 1);
+                                       _showButton.setEnabled(numSelected == 1 && foundUrl);
                                }
                        }
                });
index 562f5a474f821f3efa3adfe41f0289a18786b839..d757b017a2d6456e9e43475418116c84e6144ec3 100644 (file)
@@ -1,12 +1,14 @@
 package tim.prune.function.search;
 
 /**
- * Class to hold a search result from wikipedia / gpsies / panoramio etc
+ * Class to hold a search result from wikipedia / gpsies etc
  */
 public class SearchResult implements Comparable<SearchResult>
 {
        /** Track name or title */
        private String _trackName = null;
+       /** Point type (for POIs) */
+       private String _pointType = null;
        /** Description */
        private String _description = null;
        /** Web page for more details */
@@ -35,6 +37,22 @@ public class SearchResult implements Comparable<SearchResult>
                return _trackName;
        }
 
+       /**
+        * @param inType type of point (for POIs)
+        */
+       public void setPointType(String inType)
+       {
+               _pointType = inType;
+       }
+
+       /**
+        * @return type of point (for POIs)
+        */
+       public String getPointType()
+       {
+               return _pointType;
+       }
+
        /**
         * @param inDesc description
         */
index 9bb446fb12511ae454459400c900e3520d575340..364723712a0c785fabb6de8d9c6643d55fabef40 100644 (file)
@@ -1,26 +1,47 @@
 A Arnoia               42 14 54.95 N   8 08 04.88 W
 A Barciela, Oroso              42 57 57.49 N   8 26 31.07 W
 A Cañiza      A Cañiza is a municipality in Galicia, in the province of Pontevedra.  42 12 45.46 N   8 16 29.77 W
-A Coruña              43 21 58.95 N   8 24 28.71 W
-A Guarda               41 54 09.12 N   8 52 27.14 W
+A Coruña      A Coruña is a Galician city, in north-western Spain.   43 21 58.95 N   8 24 28.71 W
+A Estrada              41 32 35.1 N    2 12 59.63 E
+A Guarda               41 54 09.12 N   8 52 27.15 W
 A Gudiña      A Gudiña is a municipality in Galicia, in the province of Ourense.     42 03 40.13 N   7 08 34.19 W
 A Illa de Arousa       A Illa de Arousa is a municipality in Galicia, in the province of Pontevedra.   42 33 08.36 N   8 52 06.13 W
 A Merca        A Merca is a municipality in Galicia, in the province of Ourense.       42 13 22.53 N   7 54 15.55 W
 A Mezquita             42 00 42.5 N    7 02 43.17 W
-A Pobra de Trives              42 20 19.97 N   7 15 15.88 W
+A Pobra de Trives              42 20 19.96 N   7 15 15.88 W
 A Pobra do Brollón            42 33 29.3 N    7 23 40.15 W
 A Pobra do Caramiñal  A Pobra do Caramiñal is a municipality in the province of A Coruña, Galicia, Spain.   42 36 12.2 N    8 56 14.25 W
 A Rúa A Rúa is a municipality in Galicia, in the province of Ourense.        42 23 34.93 N   7 06 59.02 W
 A Veiga        A Veiga is a municipality in Galicia, in the province of Ourense.       42 15 01.36 N   7 01 30.25 W
-A Walk on Main Street, Ferndale, California    Ferndale is a small town in Eureka County, California. Its Main Street is listed by the National Register of Historic Places as a historic district.    40 34 32.16 N   124 15 54 W
+A Walk on Main Street, Ferndale, California    Ferndale is a small town in Humboldt County, California. Its Main Street is listed by the National Register of Historic Places as a historic district.  40 34 32.16 N   124 15 54 W
 A Walk up Main Street, Adamstown, Pennsylvania Adamstown is a small, historic town in Lancaster County, Pennsylvania.  40 14 36.96 N   76 03 16.2 W
+Abbaye d'Acey          47 15 42 N      5 39 25 E
+Abbaye de Bourgueil            47 16 46.2 N    0 10 18.12 E
+Abbaye de Cluny        The Abbey of Cluny (or Cluni, or Clugny) was founded on 2 September 909 and is located in the modern-day department of Saône-et-Loire in the region of Bourgogne, in east-central France, near Mâcon. 46 26 03 N      4 39 33 E
+Abbaye de Fontenay             47 38 27 N      4 23 23 E
+Abbaye de Fontevraud   Fontevraud Abbey is located near Saumur in Anjou, France        47 10 53 N      0 03 06 E
+Abbaye de La Sauve-Majeure             44 46 07.24 N   0 18 42.41 W
+Abbaye de Montmajour           43 42 20 N      4 39 50 E
+Abbaye de Saint-Germain-des-Prés              48 51 14 N      2 20 04 E
+Abbaye de Saint-Savin-sur-Gartempe             46 33 51 N      0 51 58 E
+Abbaye de Sénanque            43 55 42 N      5 11 13 E
+Abbaye de Valmagne             43 29 12.97 N   3 33 44.19 E
+Abbaye du Relec                48 26 56 N      3 43 00 W
+Abbaye Saint Wandrille Monastery Saint Wandrille in France     49 31 46.42 N   0 45 59.58 E
+Abbaye Saint-Corneille         49 25 02.6 N    2 49 28.57 E
+Abbaye Saint-Florentin, Bonneval               48 10 40.41 N   1 23 04.87 E
 Abeilhan               43 27 02 N      3 17 43 E
+Abri-caverne de l'ouvrage du Haut-Bois         47 35 24.07 N   6 48 34.23 E
+Abtei Saint-André (Lavaudieu)         45 15 49 N      3 27 17 E
 Aceldama       Aceldama or Akeldama is the Aramaic name for a place in Jerusalem associated with Judas Iscariot, one of the followers of Jesus.        31 46 05.1 N    35 13 59.51 E
 Adissan                43 32 09.96 N   3 25 45.12 E
+Agadir         30 25 00 N      9 35 00 W
 Agel           43 20 18.96 N   2 51 13.68 E
 Agrón, Ames           42 54 20.32 N   8 41 43.21 W
 Aguiño, Ribeira               42 31 24.99 N   9 00 57.6 W
-Alagna Valsesia        Alagna Valsesia is a village in Piemonte in Italy.      45 51 05.15 N   7 56 16.98 E
+Aichi prefecture               35 04 59 N      136 58 59 E
+Aiguafreda     Montseny        41 46 05.2 N    2 15 05.39 E
+Alagna Valsesia        Alagna Valsesia is a village in Piemonte in Italy.      45 51 05.16 N   7 56 16.98 E
 Albrechtsburg  The Albrechtsburg is the castle that dominates the city centre of Meißen, Germany.     51 09 59.65 N   13 28 17.83 E
 Aletsch        Aletsch region, UNESCO World Heritage Site since 2001, in Bernese Alps, Switzerland     46 27 50.9 N    8 04 20.89 E
 Allariz        Allariz is a municipality in Galicia, in the province of Ourense.       42 11 20.76 N   7 48 09.71 W
@@ -32,37 +53,39 @@ Anta de Casaínhos          38 52 50.16 N   9 10 09.91 W
 Antwerp Central Station                51 13 02 N      4 25 15.6 E
 Aquis Querquennis      Aquis Querquennis was a Roman castra at Os Baños, Bande (Ourense, Galiza).     41 58 27.2 N    7 58 52.1 W
 Arbo           42 06 39.45 N   8 18 57.47 W
+Arc de Triomf de Barcelona     The Arc de Triomf (Triumphal Arch) is an archway structure in Barcelona, Spain. 41 23 27 N      2 10 50 E
+Arc de Triomphe du Carrousel   The Arc de Triomphe du Carrousel is a triumphal arch in Paris, located in the Place du Carrousel on the site of the former Tuileries Palace.    48 51 43 N      2 19 58 E
 Arch of Constantine            41 53 23 N      12 29 27 E
 Arch of Septimius Severus (Rome)       The Arch of Septimius Severus is situated in the Forum Romanum in Rome. 41 53 34 N      12 29 05 E
 Arch of Titus  The Arch of Titus is a triumphal arch with a single arched opening, located on the Summa Sacra Via to the west of the Roman Forum in Rome.      41 53 26 N      12 29 19 E
+Arnhem         51 59 00 N      5 55 00 E
 Arenys de Munt Arenys de Munt is a village in the comarca of Maresme, Catalonia.       41 36 46 N      2 32 25 E
-Artwork:La Nouvelle Liberté           4 03 51.2 N     9 42 24.97 E
-Artwork:Lucas Grandin, Le jardin sonore de Bonamouti           4 04 19.3 N     9 42 37.91 E
-Artwork:Pascale Marthine Tayou, La Colonne Pascale             4 01 34.12 N    9 42 22.84 E
-Artwork:Tracey Rose, Oasis             4 01 28.09 N    9 42 32.75 E
 As Neves       As Neves is a municipality in Galicia, in the province of Pontevedra.   42 05 10.49 N   8 24 53.43 W
 As Pontes de García Rodríguez                43 26 47.63 N   7 51 09.54 W
 Askersund              58 53 00 N      14 54 00 E
 Assumption Cathedral in Odessa Assumption Cathedral in Odessa, Ukraine 46 28 49.43 N   30 44 21.24 E
-Australian Synchrotron         37 54 51 S      145 08 33 E
-Avión Avión is a municipality in Galicia, in the province of Ourense.        42 22 23.72 N   8 15 02.64 W
+Australian Synchrotron The Australian Synchrotron, a synchrotron facility in the suburb of Clayton, in Melbourne, Victoria, Australia. 37 54 51 S      145 08 33 E
+Avión Avión is a municipality in Galicia, in the province of Ourense.        42 22 23.73 N   8 15 02.64 W
 Ayasofya       The Church of the Holy Wisdom, commonly known as Hagia Sophia in English, is the former Greek Orthodox patriarchal cathedral, converted in 1453 to a mosque, now a museum, in Istanbul. 41 00 30.5 N    28 58 47.7 E
+Aérodrome de Besançon-Thise          47 16 25.68 N   6 04 59.09 E
+Aşgabat               37 57 00 N      58 23 00 E
 Bahnhof Dresden-Neustadt       Dresden-Neustadt railway station and the four inner city bridges        51 03 56 N      13 44 27 E
 Baiona Baiona is a municipality in Galicia, Spain in the province of Pontevedra.       42 07 03.29 N   8 51 01.86 W
 Baltar Baltar is a municipality in Galicia, in the province of Ourense.        41 57 04.75 N   7 42 56.7 W
-Barbadás              42 17 56.03 N   7 53 13.13 W
+Barbadás              42 17 56.03 N   7 53 13.14 W
+Basilique Notre-Dame de la Daurade             43 36 03 N      1 26 23 E
+Basilique Notre-Dame de Thierenbach            47 52 54.12 N   7 11 21.16 E
+Basilique Saint-Sauveur de Rennes              48 06 42 N      1 40 55 W
 Beade  Beade is a municipality in Galicia, in the province of Ourense. 42 20 06.72 N   8 08 45.05 W
-Beamish Museum: Entrance area  The entrance to Beamish Museum is on the south side of the site.        54 52 54.04 N   1 39 30.93 W
 Bergfriedhof (Stuttgart)       The Bergfriedhof is a cemetery in the Stadtbezirk Stuttgart-Ost in Stuttgart.   48 47 19.16 N   9 12 23.35 E
-Bergondo               43 19 15.35 N   8 13 54.04 W
+Bergondo               43 19 15.36 N   8 13 54.04 W
 Berlin Anhalter Bahnhof        The Anhalter Bahnhof was a large main-line railway station in Berlin. Today it is just a stop on the Berlin S-Bahn.     52 30 11 N      13 22 55 E
 Betanzos               43 16 44.24 N   8 12 55.95 W
+Bibliothèque Sainte-Geneviève                48 50 49.5 N    2 20 45 E
+Bibracte               46 55 23 N      4 02 15 E
 Biella         45 34 05.07 N   8 03 02.61 E
 Bienertparks in Dresden        Als Bienertpark werden verschiedene Parkanlagen in Dresden bezeichnet, deren Entstehung auf die Familie Gottlieb Traugott Bienert zurückgeht.  51 01 48.29 N   13 42 54.9 E
-Bildstock Austraße, Burggrumbach              49 52 19.59 N   10 01 43.48 E
-Bildstock Herrngasse 1, Grafenrheinfeld                50 00 11.52 N   10 11 54.6 E
-Bildstock Mainbacher Weg, Holzhausen           50 07 06.2 N    10 11 07.12 E
-Bildstock Schulstraße 2, Burggrumbach         49 52 13.11 N   10 02 14.06 E
+Bishkek                42 52 00 N      74 34 00 E
 Boccadasse     Boccadasse è un antico borgo marinaro situato nel levante di Genova.   44 23 23.85 N   8 58 22.71 E
 Boettcherstrasse               53 04 30 N      8 48 21 E
 Boiro          42 38 13.16 N   8 52 12.31 W
@@ -72,6 +95,7 @@ Borne des Trois Puissances    Dreiländerstein        47 30 10.58 N   7 07 48.76 E
 Borovsk        Эта страница на русском: Боровск     55 12 27.19 N   36 29 05.05 E
 Bosco delle Querce     The Regional Natural Park Bosco delle Querce (Oaks' Wood), built after the Seveso Disaster on the "A" zone.     45 38 55 N      9 09 10 E
 Botanischer Garten Heidelberg          49 25 00 N      8 40 10 E
+Brancion               46 32 51 N      4 47 48.12 E
 Brudermühlbrücke     The Brudermühlbrücke is a bridge in Munich across the Isar.   48 06 45.3 N    11 33 36.07 E
 Brudermühlstraße     The Brudermühlstraße is a street in Munich, part of the Mittlerer Ring around the city centre.        48 06 44.2 N    11 33 01.37 E
 Buenos Aires           34 36 13 S      58 22 54 W
@@ -81,16 +105,17 @@ Burg Stahleck      Stahleck Castle in Bacharach is a 12th century castle occupying a
 Burj Khalifa   The Burj Khalifa ("Khalifa Tower") is a skyscraper in Dubai, United Arab Emirates.      25 11 49.7 N    55 16 26.8 E
 Bürgerhospital (Stuttgart)            48 47 34.6 N    9 10 45.95 E
 Cabanas                43 24 50.49 N   8 10 04.15 W
-Cabo Fisterra          42 52 50.96 N   9 16 18.27 W
+Cabo Fisterra  Cape Finisterre is a rock-bound peninsula on the west coast of Galicia, in Fisterra (Spain).    42 52 50.96 N   9 16 18.27 W
 Cabrils                41 31 42 N      2 22 09 E
-Cadaqués      Cadaqués is a touristic village in Costa Brava, Alt Empordà, Catalonia, Spain 42 17 19.46 N   3 16 40.96 E
-Cal Becs (Puig-reig)           41 57 41.01 N   1 54 21.89 E
+Cadaqués      Cadaqués is a touristic village in Costa Brava, Alt Empordà, Catalonia, Spain.        42 17 19.46 N   3 16 40.96 E
 Calgary                51 02 42 N      114 03 26 W
 Cambados               42 30 52.2 N    8 48 24.89 W
 Cambre Cambre is a municipality in the province A Coruña, Galicia, Spain.     43 17 37.32 N   8 20 34.49 W
 Campo de' Fiori                41 53 43.94 N   12 28 20.06 E
+Canal du Midi  The Canal du Midi is a 240km long canal in southern France, linking the Garonne River to the Mediterranean Sea, between Toulouse and the Mediterranean port of Sète.   43 36 40 N      1 25 06 E
 Candelabro de Paracas  The Paracas Candelabra is a well-known prehistoric geoglyph found on the northern face of the Paracas Peninsula at Pisco Bay in Peru.   13 47 39.67 S   76 18 31.31 W
-Cangas         42 15 47.09 N   8 47 09.5 W
+Cangas         42 15 47.1 N    8 47 09.5 W
+Cape Arkona    There are two bunkers at Cape Arkona.   54 40 47 N      13 25 57 E
 Cape Horn      Cape Horn is often said to be the southernmost point of South America.  55 58 47 S      67 16 18 W
 Capela de São Nicolau Capela de São Nicolau ou Passos da Via Sacra. Em Portugal, Porto.      41 08 27.14 N   8 36 55.49 W
 Capriata d'Orba        Capriata d'Orba è un comune in provincia di Alessandria, Piemonte, Italia.     44 43 43.93 N   8 41 25.29 E
@@ -101,12 +126,13 @@ Cartelle  Cartelle is a municipality in Galicia, in the province of Ourense.      42 1
 Casa Buonarroti (Florence)     Casa Buonarroti è un museo di Firenze  43 46 11.64 N   11 15 49 E
 Casa de las Carnicerías, León        The Casa de las Carnicerías (butcher's shops), a monument declared as a Bien de Interés Cultural in 1992, is located at San Martín square, León (Spain).    42 35 48.84 N   5 34 04.4 W
 Casa del esquileo, Cabanillas del Monte        The casa del esquileo (shearing shed) is a monument placed at Cabanillas del Monte, Torrecaballeros, Segovia (Spain).   40 58 36 N      4 01 54 W
-Casa Milà     Also known as 'La Pedrera', due to its look.    41 23 42.65 N   2 09 42.37 E
+Casa Milà     Casa Milà, also known as La Pedrera (the open quarry), is a modernist building in Barcelona, Catalonia, Spain  41 23 42.66 N   2 09 42.37 E
+Cassine                44 45 03 N      8 31 44 E
 Castel del Monte       Castel del Monte is a castle in Apulia, in Italy.       41 05 05.28 N   16 16 15.57 E
 Castellazzo Bormida    Castellazzo Bormida is a comune (municipality) in the Province of Alessandria in the Italian region Piedmont    44 50 46 N      8 34 42 E
 Castello di Duino      Il castello di Duino è un castello situato nel comune di Duino-Aurisina, in provincia di Trieste, Italia.      45 46 18.39 N   13 36 14.3 E
 Castello di Miramare   Miramare is a castle near Trieste.      45 42 09 N      13 42 45 E
-Castello di San Giusto The statues Micheze and Jacheze in the entrance hall of Castello di San Giusto  45 38 49.99 N   13 46 23.62 E
+Castello di San Giusto The statues Micheze and Jacheze in the entrance hall of Castello di San Giusto  45 38 50 N      13 46 23.62 E
 Castello Sforzesco (Milan)     The "Castello Sforzesco" is a castle and a museum in Milan, Italy.      45 28 11.4 N    9 10 46.2 E
 Castelo da Rocha Forte         42 51 42.29 N   8 34 29.38 W
 Castelo de Castro Marim                37 13 07.43 N   7 26 29.69 W
@@ -115,37 +141,59 @@ Castelo de Monforte de Lemos              42 31 26.71 N   7 30 38.86 W
 Castelo de Monterreal          42 07 29.82 N   8 50 59.18 W
 Castelo de Pambre              42 51 34.87 N   7 56 53.7 W
 Castelo de Ribadavia           42 17 12.3 N    8 08 37.49 W
-Castelo de San Felipe          43 27 52.83 N   8 16 52.45 W
+Castelo de San Felipe  Castillo de San Felipe is a castle in the county of Ferrol, Galicia     43 27 53 N      8 16 54 W
 Castelo de Santa Cruz          43 20 54.08 N   8 21 00.64 W
 Castelo de Santo Antón                43 21 56.6 N    8 23 16.12 W
-Castelo de Sobroso     The Sobroso Castle is a castle located in Vilasobroso- Mondariz, Provincia of Pontevedra, Galicia (Spain).      42 12 17.3 N    8 27 20.51 W
+Castelo de Sobroso     The Sobroso Castle is a castle located in Vilasobroso- Mondariz, Provincia of Pontevedra, Galicia (Spain).      42 12 17.31 N   8 27 20.51 W
 Castelo de Soutomaior          42 19 46.77 N   8 34 05.54 W
 Castelo de Vilalba             43 17 52.79 N   7 40 56.44 W
 Castelo de Vilamarín          42 27 02.87 N   7 54 02.26 W
 Castiñeiras, Ribeira          42 31 57.33 N   8 59 44.67 W
 Castrelo de Miño      Castrelo de Miño is a municipality in Galicia, in the province of Ourense.     42 17 49.63 N   8 04 02.63 W
-Castrelo do Val        Castrelo do Val is a municipality in Galicia, in the province of Ourense.       41 59 26.72 N   7 25 25.2 W
-Castro Caldelas        Castro Caldelas is a municipality in Galicia, in the province of Ourense.       42 22 32.63 N   7 24 54 W
-Castro de Baroña              42 41 40.91 N   9 01 54.91 W
-Castro de Rei          43 12 32.15 N   7 23 58.38 W
+Castrelo do Val        Castrelo do Val is a municipality in Galicia, in the province of Ourense.       41 59 26.73 N   7 25 25.2 W
+Castro Caldelas        Castro Caldelas is a municipality in Galicia, in the province of Ourense.       42 22 32.62 N   7 24 54 W
+Castro de Baroña      O Castro de Baroña está situado na parroquia de Baroña no concello de Porto do Son.  42 41 40.91 N   9 01 54.91 W
+Castro de Rei          43 12 32.14 N   7 23 58.38 W
 Catacaos               5 16 00 S       80 41 00 W
-Catedral de Barcelona          41 23 02.07 N   2 10 35.19 E
-Catedral de Santiago de Compostela     Santiago de Compostela Cathedral is a Roman Catholic church in the city of the same name. It is the reputed burial-place of Saint James the Greater and the destination of the Way of St. James.        42 52 51.27 N   8 32 42.16 W
+Catedral de Santiago de Compostela             42 52 51.27 N   8 32 42.17 W
 Cathedral of Justo     The Cathedral of Justo is being built by Justo Gallego in Mejorada del Campo (Community of Madrid, Spain).      40 23 38.88 N   3 29 17.99 W
+Cathédrale Notre-Dame d'Amiens        The cathedral of Our Lady of Amiens (fr: Cathédrale Notre-Dame d'Amiens), or just Amiens Cathedral, is the tallest complete cathedral in France.       49 53 42 N      2 18 08 E
+Cathédrale Notre-Dame de Chartres             48 26 50 N      1 29 16 E
+Cathédrale Notre-Dame de Paris                48 51 10.8 N    2 20 59.28 E
+Cathédrale Notre-Dame de Reims        Cathedral "Notre-Dame de Reims", located in Reims, France       49 15 13.9 N    4 02 02.5 E
 Cathédrale Notre-Dame de Strasbourg           48 34 55 N      7 45 03 E
+Cathédrale Notre-Dame du Havre                49 29 13 N      0 06 30 E
+Cathédrale Saint-André de Bordeaux           44 50 16 N      0 34 39 W
+Cathédrale Saint-Bénigne de Dijon            47 19 17 N      5 02 04 E
+Cathédrale Saint-Julien du Mans       Interior        48 00 33 N      0 11 56 E
+Cathédrale Sainte-Cécile d'Albi              43 55 42.57 N   2 08 34.6 E
 Catoira        Catoira is a municipality in Galicia, Spain in the province of Pontevedra.      42 39 54.98 N   8 43 57.93 W
 Cedeira        Cedeira is a municipality in the province A Coruña, Galicia, Spain.    43 39 39.53 N   8 03 10.41 W
 Celanova       Celanova is a municipality in Galicia, in the province of Ourense.      42 09 07.02 N   7 57 24.65 W
 Cenlle Cenlle is a municipality in Galicia, in the province of Ourense.        42 20 36.87 N   8 05 16.17 W
 Central Park           40 46 55.2 N    73 57 57.6 W
 Cesantes, Redondela            42 18 33.05 N   8 36 44.72 W
+Champ-de-Mars (Paris)          48 51 22 N      2 17 54 E
 Chantada               42 36 27.15 N   7 46 09.2 W
-Chapela, Redondela             42 15 53.95 N   8 40 22.72 W
+Chapela, Redondela             42 15 53.95 N   8 40 22.71 W
 Chapelle de Languidou  Chapel in Plovan / Bretagne     47 54 49.13 N   4 21 09.72 W
+Chapelle Notre-Dame de Molsheim                48 32 26.16 N   7 29 40.13 E
+Chapelle Notre-Dame du Schaefertal (Soultzmatt)                47 57 06.84 N   7 12 59.4 E
 Chapelle Notre-Dame du Verger  Chapelle Notre-Dame du Verger dans l'anse du Verger à Cancale (Ille-et-Vilaine)        48 41 37 N      1 52 51 W
+Chapelle Notre-Dame-du-Chêne de Plobsheim             48 27 35.28 N   7 42 49.32 E
+Chapelle Notre-Dame-du-Grasweg de Huttenheim           48 21 31.32 N   7 34 33.85 E
+Chapelle royale Saint-Louis, Dreux             48 44 18 N      1 21 48 E
+Chapelle Saint-Gonéry         48 50 29.4 N    3 13 42.38 W
+Chapelle Saint-Sébastien de Dambach-la-Ville          48 19 37.56 N   7 25 11.75 E
+Chapelle Saint-Théodore de Vienne             45 31 28.56 N   4 52 24.13 E
+Chapelle Saint-Ulrich d'Avolsheim              48 33 43.56 N   7 30 01.51 E
+Chapelle Sainte-Marguerite d'Epfig             48 21 15.84 N   7 28 40.12 E
+Chapelle Sainte-Marie de l'Assomption d'Obersteigen            48 38 32.28 N   7 18 22.32 E
+Chapelles du Kochersberg               48 38 42 N      7 31 14.88 E
 Château d'Angers              47 28 11.67 N   0 33 33.61 W
 Château de Bugarach           42 52 36.16 N   2 21 05.15 E
 Château de Couiza             42 56 42.68 N   2 15 14.11 E
+Château de Condé             49 00 20 N      3 33 34 E
 Chiesa di Sant'Antonio in Caggiano     Church of Saint Anthony in Caggiano.    40 34 04.13 N   15 29 39.47 E
 Chioggia       Chioggia is a town in Veneto in Italy.  45 13 04.23 N   12 16 36.76 E
 Chroniques de Jérusalem       Jerusalem Mount of Olives Santa Marta Passionists church.       31 46 15.83 N   35 15 08.48 E
@@ -153,61 +201,125 @@ Church of Adelboden      The gothic village church of Adelboden was built in the 15th
 Church of Saints Apostles Peter and Paul in Vilnius            54 41 38.82 N   25 18 22.69 E
 Church of St. Johns in Vilnius Organ   54 40 57.5 N    25 17 19.22 E
 Church of São Vítor (Braga)  Igreja de São Victor em Braga  41 33 09.95 N   8 24 47.49 W
+Château d'Anet                48 51 29 N      1 26 19 E
+Château d'Arlay               46 45 32.4 N    5 32 16.44 E
+Château d'Azay-le-Rideau      The Château de Azay-le-Rideau is one of the most famous castles in t
+he French Loire Valley.        47 15 33 N      0 27 58 E
+Château d'Effiat      The château d'Effiat in Puy de Dôme, Auvergne, France.        46 02 37 N      3 15 06 E
+Château d'If          43 16 47.5 N    5 19 30.5 E
+Château d'Ussé       The Château d'Ussé is a château of the Loire Valley in Rigny-Ussé.  47 14 59 N      0 17 28 E
+Château de Blois      The Château de Blois is one of the most renowned châteaux of the Loire Valley.        47 35 07.8 N    1 19 51.42 E
+Château de Bressieux          45 19 21 N      5 16 45.01 E
+Château de Brest              48 22 52.52 N   4 29 40.95 W
+Château de Chambord           47 36 58.22 N   1 31 03.25 E
+Château de Chamerolles                48 03 37.08 N   2 09 51.01 E
+Château de Chantilly          49 11 38 N      2 29 09 E
+Château de Chenonceau         47 19 30 N      1 04 14.16 E
+Château de Cheverny           47 30 01 N      1 27 29 E
+Château de Chinon             47 10 05 N      0 14 10 E
+Château de Châteaudun                48 04 14 N      1 19 25 E
+Château de Cléron            47 05 16 N      6 03 27 E
+Château de Domfront           48 35 39 N      0 39 09 W
+Château de Fontainebleau      The Château of Fontainebleau is the largest of the French royal châteaux.     48 24 08 N      2 42 02 E
+Château de Frontenay          46 47 08.88 N   5 37 05.88 E
+Château de Gevrey-Chambertin          47 13 46 N      4 57 56 E
+Château de l'Arthaudière     The Château de l'Arthaudière is a castle in the Isère départment of the Rhône-Alpes région of France.     45 07 09 N      5 13 59 E
+Château de Lagarde            43 03 02 N      1 56 08.5 E
+Château de Langeais   The Château de Langeais in the Loire Valley    47 19 29.28 N   0 24 21.96 E
+Château de Loches     The Château de Loches in Loches is a château of the Loire Valley. The castle area, consisting of three buildings, among them one the oldest keeps in France, is one of the best preserved European architecture ensembles of the Middle Ages. 47 07 37 N      0 59 54 E
+Château de Maintenon          48 35 08 N      1 34 41 E
 Château de Malmaison  Château de Malmaison was the place of residence of Joséphine de Beauharnais and Napoleon Bonaparte    48 52 15 N      2 10 01 E
+Château de Montfaucon         47 14 46.32 N   6 04 42.24 E
+Château de Murol              45 34 42 N      2 56 43 E
+Château de Pagax              44 36 29.88 N   2 15 06.98 E
 Château de Puivert            42 55 16.21 N   2 03 17.82 E
+Château de Saint-Germain-en-Laye      The Château de Saint-Germain-en-Laye is a former royal residence in Saint-Germain-en-Laye, located ca. 19 km to the west of Paris. Nowadays it serves as Musée des Antiquités Nationales.    48 53 53 N      2 05 47 E
+Château de Saint-Izaire       The Château de Saint-Izaire is a castle in the Saint-Izaire commune of the Aveyron département of France      43 58 31 N      2 43 10 E
+Château de Suscinio           47 30 46 N      2 43 46 W
+Château de Suze-la-Rousse             44 17 14.64 N   4 50 11.62 E
+Château de Thorens    The Château de Thorens is a castle in the commune of Thorens-Glières in the Haute-Savoie département of France.      45 59 37 N      6 15 20 E
+Château de Valençay  The Château de Valençay is one of the châteaux of the Loire Valley in the french region Centre.      47 09 27 N      1 33 48 E
+Château de Vaux-le-Vicomte            48 33 53.46 N   2 42 50.4 E
+Château de Wangenbourg        The castle of Wangenbourg is a mediaeval castle in Wangenbourg-Engental, Bas-Rhin, France.      48 37 15 N      7 18 51 E
+Château des Adhémar          44 33 33.02 N   4 45 15.35 E
+Château du Haut-Kœnigsbourg  The castle of Haut-Kœnigsbourg is a mediaeval castle located in Orschwiller, France.   48 14 58 N      7 20 39 E
+Château-Gaillard              49 14 17.26 N   1 24 09.91 E
+Cimetière du Montparnasse     The Montparnasse cemetery (Fr: Cimetière du Montparnasse) is a famous cemetery in the Montparnasse quarter of Paris, France.   48 50 17 N      2 19 37 E
+Cimetière du Père-Lachaise   Père Lachaise Cemetery (French: Cimetière du Père-Lachaise) (officially, cimetière de l'Est “eastern cemetery”) is the largest cemetery in the city of Paris at 118 acres (48 ha), though there are larger cemeteries in Paris suburbs. 48 51 43 N      2 23 39 E
 Cinque Terre   The Cinque Terre are five coastal villages in the province of La Spezia, Italy. 44 06 37.74 N   9 44 31.35 E
+Ciudad Real            38 59 00 N      3 55 00 W
+Clermont-Ferrand               45 46 47 N      3 05 13 E
+Clos Vougeot           47 10 29.72 N   4 57 19.74 E
 Collingwood Monument   A monument to Admiral Collingwood (1748-1810) was erected in Tynemouth, North East England, in 1845.    55 00 52.96 N   1 25 12.14 W
+Collégiale Notre-Dame de Melun                48 32 08 N      2 39 37 E
 Collégiale Saint-Florent de Niederhaslach             48 32 35 N      7 20 29 E
 Collégiale Saint-Martin de Colmar             48 04 38.5 N    7 21 29 E
 Collégiale Saint-Thiébaut de Thann           47 48 40 N      7 06 06 E
+Collégiale Saints-Michel-et-Gangolphe de Lautenbach           47 56 27.96 N   7 09 32.94 E
 Colonia-Haus   The Colonia-Haus is a 45-storey, 147 m skyscraper completed in 1973 in the Riehl district of Cologne, Germany.  50 57 38 N      6 58 55 E
+Colonne Vendôme               48 52 02.89 N   2 19 45.89 E
 Comacchio      Comacchio is a town in Emilia Romagna in Italy. 44 41 31.74 N   12 10 55.95 E
 Concatedral de Santa María de Guadalajara             40 38 04.72 N   3 09 45.15 W
-Corrubedo, Ribeira             42 34 21.64 N   9 04 22.77 W
+Conciergerie   The Conciergerie in the Palais de Justice, Paris, France        48 51 23 N      2 20 44 E
+Corrubedo, Ribeira             42 34 21.64 N   9 04 22.76 W
 Cortegada      Cortegada is a municipality in Galicia, in the province of Ourense.     42 12 24.86 N   8 10 01.06 W
 Coulée verte René-Dumont     The Promenade plantée is an elevated park in the 12th arrondissement of Paris, France. 48 50 32 N      2 23 15 E
+Couvent de Bischenberg (Bischoffsheim)         48 28 59.88 N   7 28 46.34 E
 Couvent de la Divine Providence de Saint-Jean-de-Bassel                48 48 12.12 N   6 59 32.06 E
+Couvent de Reinacker           48 40 51.96 N   7 24 27.5 E
 Crecente       Crecente is a municipality in Galicia, in the province of Pontevedra.   42 09 07.02 N   8 13 22.52 W
+Credit Lyonnais Head Office            48 52 14.95 N   2 20 11.45 E
+Château de Crussol    The Château de Crussol is a castle in the Ardèche départment of France.      44 56 18 N      4 51 09 E
 Cualedro               41 59 18.46 N   7 35 40.71 W
 Culleredo      Culleredo is a municipality in the province A Coruña, Galicia, Spain.  43 17 31.02 N   8 23 08.99 W
 Curtis Hall Arboretum          40 04 42 N      75 07 44 W
+Cáceres       Cáceres is the capital of Cáceres Province, in Extremadura, Spain.    39 28 23 N      6 22 16 W
 Córdoba (Argentina)   Intendancy square       31 24 00 S      64 11 00 W
+Dakar          14 43 55 N      17 27 26 W
 Dalhems kyrka  Die Kirche von Dalhem zählt zu den berühmtesten auf Gotland. Ihr Turm, der im 14. Jahrhundert angefügt wurde, gehört zu den höchsten der Landkirchen Gotlands.     57 33 08.7 N    18 32 03.2 E
 Dazaifu Tenman-gū             33 31 17.49 N   130 32 05.45 E
 Dent du Géant         45 51 43 N      6 57 06 E
 Detroit Institute of Arts      The Detroit Institute of Arts is a large art museum in Detroit, Michigan in the United States.  42 21 33.5 N    83 03 53.3 W
-Deutsches Museum Verkehrszentrum               48 07 57.85 N   11 32 40.69 E
+Deutsches Museum Verkehrszentrum       München        48 07 57.85 N   11 32 40.69 E
 Deyrulzaferan          37 17 57.6 N    40 47 33.9 E
-Dorfkirche Zernikow            53 05 24.62 N   13 05 23.78 E
+Dorfkirche Klein Haßlow       Church in Klein Haßlow, Wittstock municipality, Ostprignitz-Ruppin district, Brandenburg state, Germany        53 10 29.84 N   12 31 28.6 E
+Dorfkirche Priort      Church in Priort, Wustermark municipality, Havelland district, Brandenburg state, Germany.      52 30 44.61 N   12 57 49.36 E
 Dozón         42 34 09.35 N   8 03 08.25 W
 Drexel University              39 57 14.68 N   75 11 12.76 W
 Duchesse Anne (voilier)                51 02 15.37 N   2 22 20.21 E
-Duino-Aurisina English:        45 45 02.29 N   13 40 29.6 E
+Duino-Aurisina         45 45 02.29 N   13 40 29.6 E
 Durban         29 53 00 S      31 03 00 E
 Dürrenstein (Südtirol)       The Dürrenstein is a mountain in the Dolomites in South Tyrol. 46 40 24 N      12 11 04 E
-Edinburgh Park & Ride network  The Park & Ride network serving Edinburgh, Scotland. Services run by Lothian Buses call at all the sites except Ferrytoll.      55 56 30.52 N   3 00 42.77 W
 Effnerplatz    The Effnerplatz is a square in the north of Munich, in the Borough Bogenhausen. 48 09 11.79 N   11 36 54.73 E
 Église Saint-Jean-Baptiste de Laure-Minervois         43 16 20.78 N   2 31 12.11 E
+Église Saint-Louis-en-l'Île  The Saint-Louis-en-l'Île Church (lit. "St. Louis on the Island"), is a Catholic church located on Île Saint-Louis in the IVe arrondissement of Paris  48 51 4.38 N 2 21 27.47 E
 Église Saint-Martin de Chambonas              44 25 02.45 N   4 07 43.97 E
 Eglise Saint-Pierre des Cuisines               43 36 15.49 N   1 26 08.26 E
 Église Saint-Vincent-de-Paul (Paris)  Saint-Vincent-de-Paul is a church in Paris near the Gare du Nord        48 52 43.7 N    2 21 06.6 E
-Empúries      Empúries is a town in the Mediterranean coast of the Catalan comarca of Empordà. It was founded by the ancient Greeks with the name of Ἐμπόριον (Emporion — "market").     42 08 20.29 N   3 07 11.19 E
-Erica, Victoria        Erica, Victoria 37 59 00 S      146 22 00 E
+El Padul               37 01 27 N      3 37 36 W
+El Vendrell            41 13 11.72 N   1 32 04.08 E
+Empúries      Empúries is a town on the Mediterranean coast of the Catalan comarca of Alt Empordà in Catalonia, Spain.      42 08 20.29 N   3 07 11.19 E
+Enclos paroissial de Saint-Thégonnec          48 31 13.4 N    3 56 47.44 W
+Erica, Victoria         37 59 00 S     146 22 00 E
 Erlöserkirche (Bad Homburg)   The Church of the Redeemer is a protestant church in Bad Homburg, Germany.      50 13 35.5 N    8 36 42 E
 Esgos          42 19 29.71 N   7 41 45.94 W
-Eurockéennes 2015             47 41 08.79 N   6 48 30.36 E
-European Parliament            48 35 51.82 N   7 46 09.82 E
+European Parliament    The European Parliament is the parliament of the European Union.        48 35 51.82 N   7 46 09.82 E
+Familistère           49 54 15 N      3 37 31 E
 Fangelsbachfriedhof    The Fangelsbachfriedhof is one of the most important historical cemeteries in Stuttgart.        48 45 56 N      9 10 28 E
 Feldkommandostelle Hegewald (East Prussia)             54 08 05.54 N   21 58 36.02 E
 Fene   Fene is a municipality in the province A Coruña, Galicia, Spain.       43 27 40.11 N   8 11 28.2 W
-Ferrol         43 29 21.46 N   8 13 29.94 W
+Ferrol         43 29 21.47 N   8 13 29.94 W
 Ferrytoll Park & Ride  Ferrytoll Park & Ride is a bus/car interchange in Fife, Scotland, at the northern end of the Forth road crossing.       56 01 21.47 N   3 24 22.97 W
 Fieschergletscher              46 30 07.88 N   8 08 30.59 E
-Fiesole (area archeologica)            43 48 29.05 N   11 17 39.19 E
-Fish Creek, Victoria   Fish Creek, Victoria    38 41 00 S      146 05 00 E
+Fiesole (area archeologica)            43 48 29.04 N   11 17 39.19 E
+Fish Creek, Victoria           38 41 00 S      146 05 00 E
 Flaucher       Flaucher is an area in the south of Munich on the left and right hand side of the Isar (district: Sendling and Thalkirchen).    48 06 27 N      11 33 27 E
+Fontaine des Neuf-Canons               43 31 36.12 N   5 26 55.25 E
+Fontaine du Palmier            48 51 26.99 N   2 20 50.17 E
+Fort Boyard    Fort Boyard is a fort located between the île d'Aix and the île d'Oléron in the Pertuis d'Antioche straits, on the west coast of France.     45 59 58.71 N   1 12 50.16 W
 Fort de Bessoncourt            47 38 58.41 N   6 55 37.74 E
 Fort de la Miotte              47 38 53.29 N   6 52 30.33 E
+Fort de Vézelois              47 36 01.67 N   6 54 29.41 E
 Fort des Basses Perches                47 37 34.05 N   6 52 06.81 E
 Fort du Bois d'Oye             47 34 27.7 N    6 50 36.55 E
 Fort Ross      Fort Ross, a former Russian fur trade outpost, located on the coast of Northern California (United States).     38 30 51.35 N   123 14 36.88 W
@@ -218,40 +330,49 @@ Fortezza del Priamar      La fortezza del Priamar è un antico insediamento storico p
 Forêt domaniale de Sète      The National Forest of Sète in the commune of Sète, Hérault, France. 43 24 18.74 N   3 40 13.74 E
 Fosse De Sessevalle            50 22 11.03 N   3 15 41.09 E
 Four solaire d'Odeillo The "Centre du Four Solaire Félix Trombe" is located in Odeillo, France.       42 29 37 N      2 01 45 E
-Francelos, Ribadavia           42 16 35.51 N   8 09 34.87 W
+Francelos, Ribadavia           42 16 35.51 N   8 09 34.88 W
 Franciscan Monastery in Katowice Panewniki     Neo-Romanesque monastery of the Franciscans in Katowice Panewniki in Poland from the early XX century.  50 13 37 N      18 57 45 E
 Pfarrkirche St. Bartholomäus in Friesach              46 57 04.28 N   14 24 18.19 E
 Fubine Fubine è un comune in provincia di Alessandria, Piemonte, Italia.      44 57 55.35 N   8 25 32.75 E
+Funkturm Leipzig               51 18 49.02 N   12 23 34.93 E
 Fuzhou Fuzhou, also known as Foochow, is a city in China.      26 04 16 N      119 18 13 E
 Gandino                45 48 42 N      9 54 11 E
 Gardens of Nymphenburg Palace  Der Nymphenburger Schlosspark ist eines der größten und bedeutendsten Gartenkunstwerke Deutschlands.  48 09 29 N      11 30 13 E
+Gare de Metz-Ville             49 06 35.28 N   6 10 39 E
+Gare de Paris-Est      Literally East Station, Paris, but usually called Gare de l'Est in Paris, France.       48 52 37 N      2 21 33 E
+Gare de Paris-Nord     Literally North Station, Paris, but usually called Gare du Nord in Paris, France.       48 52 58 N      2 21 24 E
+Gare de Paris-Saint-Lazare     Gare Paris Saint-Lazare is one of the six large terminus railway stations of Paris.     48 52 37 N      2 19 28 E
 Garmischer Straße     The Garmischer Straße is a street in Munich, part of the Mittlerer Ring around the city centre.        48 07 28.03 N   11 31 14.51 E
 Garten des Himmlischen Friedens        The Garten des Himmlischen Friedens (lit. Garden of Heavenly Peace) is a small walled Chinese garden in the Bethmannpark in Frankfurt-Nordend   50 07 07.57 N   8 41 26.52 E
 Gavi   Gavi is a town in Piemonte in Italy.    44 41 19.35 N   8 48 10.64 E
 Gedankengang Offenbach This is part of a series of tunnels in Offenbach that have been redesigned.     50 05 58.64 N   8 45 49.92 E
-Gent-Wevelgem 2014             50 48 29.25 N   3 10 49.47 E
 Georg-Brauchle-Ring    The Georg-Brauchle-Ring is a street in Munich.  48 10 34.46 N   11 32 35.48 E
-Gernikako Arbola       The Gernika oak where the lords of Biscay (including several kings of Castile and Spain) came to take the oath of respect to the basques Fueros (Rules and Rights).     43 18 53.44 N   2 40 47.92 W
+Gernikako Arbola       The Gernika oak where the lords of Biscay (including several kings of Castile and Spain) came to take the oath of respect to the basques Fueros (Rules and Rights).     43 18 53.43 N   2 40 47.92 W
 Giardino dei Semplici di Firenze       The Orto Botanico di Firenze (2.3 hectares), also known as the Giardino dei Semplici, is a botanical garden maintained by the University of Florence.   43 46 45 N      11 15 39 E
-Giurtelecu Şimleului  Giurtelecu Şimleului is a settlement in Romania.       47 18 N 22 48 E
+Giurtelecu Șimleului  Giurtelecu Șimleului is a settlement in Romania.       47 18 N 22 48 E
+Glanum         43 46 26 N      4 49 57 E
 Goethedenkmal (Wien)   The Goethe monument at the Opernring in Vienna by Edmund Hellmer        48 12 12.07 N   16 21 57.73 E
 Gogar Tram Depot               55 56 22.27 N   3 19 36.44 W
 Gorle          45 42 14 N      9 43 08 E
 Government House, Jersey               49 11 41.44 N   2 05 39.78 W
-Gradara        Gradara is a town in Marche, Italy.     43 56 15.75 N   12 45 59.24 E
+Gradara        Gradara is a town in Marche, Italy.     43 56 15.75 N   12 45 59.25 E
+Grand Hôtel de Cabourg                49 17 37.32 N   0 06 59.08 W
+Grand Palais   The Grand Palais in Paris, France       48 51 58 N      2 18 45 E
 Granollers     Granollers is a city near Barcelona, in Catalonia, Spain.       41 32 35.1 N    2 12 59.63 E
+Gromo  Gromo is a town in Lombardia in Italy.  45 58 09.91 N   9 55 25.72 E
 Gråmanstorps kyrka            56 08 59.33 N   13 06 00.4 E
-Guaricano      Barrio of Santo Domingo (Dominican Republic), Genoese mission   18 32 01.81 N   69 55 58.57 W
 Gut Böckel    Böckel Castle in Rödinghausen, District of Herford, North Rhine-Westphalia, Germany.  52 13 29.1 N    8 31 02.98 E
 Gymnasium Koblenzer Straße    The Gymnasium Koblenzer Straße, also known as Kobi, is a German grammar school in Urdenbach, an urban borough of Düsseldorf.  51 08 58.67 N   6 53 08.95 E
 Göltzschtalbrücke            50 37 21.29 N   12 14 37.46 E
 Hamburg        Hamburg is a City-State in the North of Germany and one of the biggest seaports in Europe.      53 34 07 N      10 02 19 E
 Hamburger Rathaus              53 33 01 N      9 59 32 E
+Hameau de la Reine             48 49 07 N      2 06 46 E
+Hampigny               48 27 21 N      4 35 24 E
 Hansestaden Visby      The Hanseatic town Visby was founded in the 10th century, on the then independent Baltic Sea island of Gotland. 57 38 20 N      18 17 40 E
+Château d'Harcourt    The Château d'Harcourt is a castle located in the commune of Harcourt in the Eure département of France       49 10 26 N      0 47 11 E
 Haus Werburg   Haus Werburg is a small water castle in Spenge, Kreis Herford.  52 08 30.78 N   8 28 33.66 E
 Hazmburk       Hazmburk is a hill with castle in České středohoří in Czech Republic.      50 26 03 N      14 00 52 E
 Heckenstallerstraße   The Heckenstallerstraße is a street in Munich, part of the Mittlerer Ring around the city centre.      48 06 35.77 N   11 31 42 E
-Heiligenhäuschen Neun Morgen, Burggrumbach            49 52 42.52 N   10 01 26.96 E
 Hellig Kors Kirke      Hellig Kors Kirke is a church on Nørrebro in Copenhagen, Denmark.      55 41 15.72 N   12 33 05.04 E
 Henninger-Turm The 120-m-high Henninger Turm is located in Frankfurt-Sachsenhausen in Germany. 50 05 50.16 N   8 41 36.77 E
 Herford        Herford is a city in North Rhine-Westphalia, Germany.   52 06 57.81 N   8 40 12.11 E
@@ -260,61 +381,80 @@ Holy Trinity Cathedral in Odessa          46 28 34.36 N   30 44 18.43 E
 Hoppenlaufriedhof      The Hoppenlaufriedhof is a cemetery in Stuttgart.       48 46 54 N      9 10 05 E
 Horyuji        Hōryū-ji (法隆寺, "Temple of the Flourishing Law") is a Buddhist temple in Ikaruga, Nara Prefecture, Japan.        34 36 53.06 N   135 44 03.02 E
 Hospitalkirche (Stuttgart)             48 46 40 N      9 10 22 E
-Hôtel du Gouverneur, Belfort          47 38 16.16 N   6 51 49.16 E
+Humprecht      Humprecht is a castle on top of a hill near Sobotka in the Hradec Králové region in the Czech Republic.       50 28 13 N      15 10 11 E
+Hôtel Biron           48 51 19 N      2 18 57 E
+Hôtel d'Ulmo          43 35 51 N      1 26 59.28 E
+Hôtel de Bourgtheroulde               49 26 31.2 N    1 05 17.77 E
+Hôtel de Sens         48 51 12 N      2 21 33 E
+Hôtel de Soubise              48 51 38 N      2 21 30 E
+Hôtel Lutetia         48 51 04 N      2 19 39 E
+Hôtel Négresco               43 41 40 N      7 15 27 E
 Igreja de São Martinho de Aldoar              41 10 14.58 N   8 40 13.83 W
 Igrexa de San Pedro de Vilanova de Dozón      The romanic parochial Church of San Pedro of Dozón     42 35 06.77 N   8 01 27.02 W
 Igrexa de Santa María de Cambre               43 17 32.03 N   8 20 34.4 W
 Igrexa de Santo Antolín de Toques             42 58 41.36 N   7 58 59.23 W
 Illa de Cortegada              42 37 05.87 N   8 47 02.7 W
 Iloilo City    The City of Iloilo is the capital city of the Provinces of the Philippines of Iloilo.   10 41 24 N      122 33 00 E
-Im Klosterhof Nr 4, Kennelbach Vbg, Aufbahrungshalle           47 28 49.67 N   9 45 59.27 E
-Info:Kusterdingen      Kusterdingen is a municipality situated between Tübingen and Reutlingen in Baden-Württemberg in Germany.      48 31 20.28 N   9 07 15.24 E
-Info:Mössingen        Mössingen is a municipality circa 15km south of Tübingen in Baden-Württemberg in Germany.    48 24 23 N      9 03 27 E
+Innerer Plauenscher Friedhof   Cemetery „Innerer Plauenscher Friedhof“ near church „Auferstehungskirche“ in Dresden-Plauen     51 01 42.38 N   13 42 15.91 E
 Institut de théologie orthodoxe Saint-Serge           48 52 59.98 N   2 23 01.23 E
 Isarring       The Isarring is a street in Munich, part of the Mittlerer Ring around the city centre.  48 09 36.65 N   11 36 03.89 E
-Isla San Carlos        Peninsula of San Carlos, commonly known as San Carlos Island, part of Venezuela, is located north of the island of Toas, between the Gulf of Venezuela and Lake Maracaibo.      10 59 42.73 N   71 36 43.16 W
+Isfahan اصفهان           32 39 05 N      51 40 45 E
+Isla San Carlos        Isla San Carlos or peninsula of San Carlos, is part of Venezuela and is located north of the island of Toas.    10 59 42.73 N   71 36 43.16 W
 Ivrea  Ivrea is a town in Piemonte in Italy.   45 27 50.4 N    7 52 42.96 E
 Jagdschloss Wermsdorf          51 16 59.52 N   12 56 21.85 E
-Jesuitenkirche (Wien)  The Jesuit church is a prominent church in Vienna, Austria.     48 12 32.95 N   16 22 39.48 E
+Jardin de l'État      The Jardin de l'État is a botanical garden on Réunion island. 20 53 12 S      55 27 04 E
+Jardin des Tuileries           48 51 50 N      2 19 34 E
+Jaroměř      Pond    50 21 22.45 N   15 55 17.21 E
+Jesuitenkirche (Wien)  The Jesuitenkirche (Jesuit church) is a prominent church in Vienna, Austria.    48 12 32.95 N   16 22 39.48 E
 Jin Mao Tower  Jin Mao Building        31 14 14 N      121 30 05 E
 Johannesburg   Johannesburg is the largest city in South Africa.       26 12 16 S      28 02 44 E
 Jubiläumssäule               48 46 42.85 N   9 10 47.53 E
 Jumkils kyrka  Jumkils kyrka tillhör Bälingebygdens församling, Upplands västra kontrakt, Uppsala stift / Diocese of Uppsala.      59 56 33.2 N    17 25 23.7 E
+Jüdischer Friedhof Haunetal           50 45 12.6 N    9 41 00.6 E
 Kaaba          21 25 21.11 N   39 49 34.41 E
 Kagawa prefecture      Kagawa Prefecture (香川県, Kagawa-ken?) is a prefecture of Japan located on Shikoku island. The capital is Takamatsu.        34 20 24.4 N    134 02 35.8 E
 Kansai International Airport   Kansai International Airport, is an international airport located on an artificial island in the middle of Osaka Bay, off the shore of Sennan district of Osaka, Japan. 34 26 03 N      135 13 58 E
 Karlsfried     Karlsfried Castle is situated nearby the town of Zittau in Sachsen, Germany.    50 50 05.05 N   14 47 32.29 E
 Katharinenhospital Stuttgart           48 48 01.87 N   9 12 21.6 E
-Kennesaw State Soccer Stadium  New KSU Stadium is a new stadium near Kennesaw, Georgia that is currently being constructed.    34 01 46.43 N   84 34 03.63 W
-Kiel   Kiel is the Capital city and most populous city of the northern German state Schleswig-Holstein.        54 19 31 N      10 08 26 E
-Kleinmarkthalle Frankfurt              50 06 45 N      8 41 01 E
+Kathmandu              27 43 00 N      85 22 00 E
+Kiel   Kiel is the capital city and most populous city of the northern German state Schleswig-Holstein.        54 19 31 N      10 08 26 E
+Kleinmarkthalle Frankfurt      Vegetable stall 50 06 45 N      8 41 01 E
+Klimkówka - stary dwór               49 35 18.78 N   21 49 50.14 E
 Kloster Ettal  The monastery of Ettal is a Benedictine monastery in Bavaria/Germany near Oberammergau. 47 34 09.33 N   11 05 40.42 E
 Kotohira Gu    Kotohira Gu in Kotohira, Kagawa prefecture, Japan.      34 11 02.2 N    133 48 34 E
 Kreis Herford          52 06 57.54 N   8 39 40.42 E
 Kreis Minden-Lübbecke Der Kreis Minden-Lübbecke ist ein Landkreis im Osten des Landes Nordrhein-Westfalen mit Sitz in Minden.        52 16 53.48 N   8 54 41.71 E
-Kreuzschlepper Am Weiher 1, Holzhausen         50 07 06.2 N    10 11 07.12 E
+Kreuz im Heldenhain, Geschwister-Scholl-Weg, Ruhland           51 27 30.61 N   13 52 01.48 E
+Kriegerdenkmal in Arnsdorf (Ruhland)           51 25 48.02 N   13 51 05.5 E
 Kronprinzenpalais (Stuttgart)          48 46 41.56 N   9 10 39.88 E
 Krumbach (Schwaben)    Krumbach (Schwaben) ist eine Stadt im Landkreis Günzburg, Regierungsbezirk Schwaben, Bayern.   48 14 35 N      10 21 48 E
 Krummesse      Krummesse is a village in Schleswig-Holstein, Germany, which partly belongs to Kreis Herzogtum Lauenburg and partly to Lübeck. 53 46 45 N      10 38 30 E
-Kuala Lumpur           3 08 08.5 N     101 41 16.8 E
+Kuala Lumpur           3 09 35 N       101 42 00 E
 Kunsthalle Bremen              53 04 22 N      8 48 49 E
 Kuressaare             58 09 00 N      22 16 48 E
 Kusterdingen           48 31 20.28 N   9 07 15.24 E
 Lac Blanc (Orbey)      Dans le massif des Vosges, près d'Orbey.       48 07 31.32 N   7 05 34.95 E
+Lac Chambon            45 34 13 N      2 55 18 E
 Lac de Serre-Ponçon   Le Lac de Serre-Ponçon, vallé de l'Ubaye, Hautes-Alpes, France        44 30 33.26 N   6 22 10.8 E
+Lac des Dix            46 03 25.74 N   7 23 51.17 E
 Lalín Lalín is a municipality in Galicia, Spain in the province of Pontevedra.       42 39 36.8 N    8 06 43.31 W
-Langes Tannen  The domain Langes Tannen now belongs to the town of Uetersen.   53 41 32.67 N   9 40 28.6 E
-Large Hadron Collider          46 16 17 N      6 03 48.5 E
+Langes Tannen          53 41 32.67 N   9 40 28.6 E
+Large Hadron Collider  CERN collider near Geneva, Switzerland  46 16 17 N      6 03 48.5 E
 Larouco        Larouco is a municipality in Galicia, in the province of Ourense.       42 20 48.74 N   7 09 37.22 W
-Laza   Laza is a municipality in Galicia, in the province of Ourense.  42 03 36.69 N   7 27 37.14 W
-Le Crozet (Loire)      Le Crozet est une commune française, située dans le département de la Loire et la région Rhône-Alpes.      46 10 11 N      3 51 25 E
+Lavoir de Gex          46 20 02.04 N   6 03 30.02 E
+Laza   Laza is a municipality in Galicia, in the province of Ourense.  42 03 36.68 N   7 27 37.14 W
+Le Louxor              48 52 00 N      2 20 59 E
 Le Sauze-du-Lac        Le Sauze-du-Lac est un petit village de les Hautes-Alpes, prés du lac de Serre-Ponçon.        44 28 42.94 N   6 18 52.35 E
+Le Train Bleu          48 50 42 N      2 22 24 E
+Legnano        Legnano is a town in the north-west of Lombardy, situated on the flat lands of the Po Valley between Milan and Lake Maggiore.   45 35 48.84 N   8 54 32 E
 Leiro  Leiro is a municipality in Galicia, in the province of Ourense. 42 22 11.51 N   8 07 27.42 W
+Les Invalides          48 51 18 N      2 18 45 E
 Levanto        Levanto is a village in Liguria in Italy.       44 10 15.82 N   9 36 41.68 E
+Lighthouses at Cape Arkona     There are two lighthouses and one bearing tower at Cape Arkona. 54 40 47 N      13 25 57 E
 Ligne Aubagne - Fuveau         43 23 41.38 N   5 33 59.44 E
 Lima           12 05 36 S      77 02 48 W
 Loro Parque    Puerto de la Cruz, Tenerife, Canarias, España  28 24 30.18 N   16 33 51.25 W
-Lothian Buses: Central garage  Central garage in Annandale Street, Broughton, is one of Lothian Buses' depot sites.    55 57 40.43 N   3 11 16.73 W
+Loschwitzer Friedhof   Cemetery "Loschwitzer Friedhof" in Dresden-Loschwitz    51 02 46 N      13 49 18.98 E
 Luise-Kiesselbach-Platz        The Luise-Kiesselbach-Platz is a square in the southwest of Munich.     48 06 44.17 N   11 31 03.27 E
 Lunds domkyrka Lunds domkyrka or Lund Cathedral is the cathedral of Lund in Skåne in southern Sweden. 55 42 14.59 N   13 11 36.91 E
 LWL-Freilichtmuseum Detmold    The LWL-Freilichtmuseum Detmold (earlier name: Westfälisches Freilichtmuseum Detmold) is a museum for folklife studies in the town of Detmold, Germany.        51 55 25 N      8 52 12 E
@@ -322,13 +462,16 @@ Madone            45 39 00 N      9 32 53 E
 Magasin-caverne du Salbert             47 39 20.34 N   6 49 22.79 E
 Magazzini del Cotone di Genova I Magazzini del Cotone sono una delle principali strutture del porto antico di Genova.  44 24 29.75 N   8 55 16.68 E
 Mahabodhi Temple       The Mahabodhi Temple is a Buddhist temple in Bodh Gaya, the location where Siddhartha Gautama, the Buddha, attained enlightenment.      24 41 46 N      84 59 29 E
+Maison de François Coignet            48 55 51.24 N   2 20 30.44 E
+Maison de la Mutualité                48 50 55.5 N    2 21 02 E
 Maisons de la rue Jeanne-Mance         45 30 31.9 N    73 34 11.02 W
 Manneken Pis van Brussel       Manneken Pis in Brussels        50 50 42 N      4 21 00 E
-Manzaneda      Manzaneda is a municipality in Galicia, in the province of Ourense.     42 18 35.34 N   7 13 58.01 W
-Mary's Tomb    'egg' (ornament hung on oil lamp chains) with winged seraphim. Armenian artwork 31 46 48.5 N    35 14 21.41 E
+Manufacture nationale de Sèvres               48 49 43 N      2 13 21 E
+Manzaneda      Manzaneda is a municipality in Galicia, in the province of Ourense.     42 18 35.33 N   7 13 58.01 W
 Marín         42 23 31.28 N   8 42 16.58 W
 Maside Maside is a municipality in Galicia, in the province of Ourense.        42 24 44.75 N   8 01 31.85 W
 Matitone (Genova)      Il Matitone è un grattacielo di Genova dalla struttura a forma di lapis. È situato nella zona portuale di San Benigno, a breve distanza dalla torre della Lanterna.   44 24 40.59 N   8 54 25.07 E
+Matteus kyrka, Stockholm               59 20 43.08 N   18 02 32.94 E
 Maximiliansbrücke in München         48 08 12.42 N   11 35 31.45 E
 Meaño Meaño is a municipality in Galicia, Spain in the province of Pontevedra.       42 26 31.95 N   8 46 46.02 W
 Meis   Meis is a municipality in Galicia, Spain in the province of Pontevedra. 42 30 49.01 N   8 41 27.14 W
@@ -338,27 +481,27 @@ Millennium Town Park      The Millennium Town Park is a public park in Saint Helier,
 Minden Minden is a German city in North Rhine-Westphalia.      52 17 20.18 N   8 55 04.19 E
 Mindener Dom   Cathedral in Minden, District of Minden-Lübbecke, North Rhine-Westphalia, Germany.     52 17 19.85 N   8 55 09.94 E
 Mindener Kreisbahnen   Kreisbahnen Minden, ein Unternehmen aus dem 19. Jahrhundert, das in Abwandlungen noch heute besteht.    52 18 01.14 N   8 54 50.98 E
-Miquel Marti i Pol.jpg Monument al Llibre, Saltamartí 41 23 21.34 N   2 10 05.05 E
 Mittagskogel   Mittagskogel is a peak in the Karawanken mountain chain in Carinthia / Austria / EU.    46 30 26.54 N   13 57 08.1 E
 Moaña Moaña is a municipality in Galicia, Spain in the province of Pontevedra.       42 17 05.52 N   8 44 57.56 W
-Moe, Victoria  Moe, Victoria   38 10 20 S      146 16 04 E
+Moe, Victoria          38 10 20 S      146 16 04 E
 Molino Stucky  Il Molino Stucky è uno storico edificio di Venezia. È un esempio di architettura industriale neogotica.       45 25 41.55 N   12 19 11.95 E
-Mollet del Vallès     Mollet del Vallès is a village in the comarca of Vallès Oriental (Catalonia, Spain).  41 32 35.1 N    2 12 59.63 E
 Monastery of San Paio de Diomondi              42 36 13.6 N    7 42 34.1 W
-Monforte de Lemos              42 31 19.98 N   7 30 46.55 W
+Monforte de Lemos              42 31 19.98 N   7 30 46.56 W
 Monte Amiata   Il Monte Amiata è un monte situato nella Toscana.      42 54 00 N      11 38 00 E
 Monte Musinè  el Monte Musinè es una cima de los Alpes Grayos, en Italia.    45 06 50 N      7 27 16 E
 Monterrei      Monterrei is a municipality in Galicia, in the province of Ourense.     41 56 51.19 N   7 26 58.52 W
 Montevideo             34 52 01 S      56 10 00 W
-Montsec d'Ares El Montsec d'Ares és el sector central de la Serra del Montsec, entre els congostos deTerradets a l'est i el de Mont-rebei a l'oest    42 02 27.96 N   0 47 33.68 E
+Montmartre Cemetery    Montmartre Cemetery (Fr: Cimetière de Montmartre) is a famous cemetery located at 37 Avenue Samson, in the 18th arrondissement of Paris, France.       48 53 16 N      2 19 49 E
 Monument de Joseph Sec Monument Joseph Sec, 8 avenue Pasteur, Aix-en-Provence, France. 43 31 59.44 N   5 26 46.71 E
 Monument international de la Réformation              46 12 00.78 N   6 08 45.19 E
-Mos    Mos is a municipality in Galicia, Spain in the province of Pontevedra.  42 11 39.08 N   8 39 11.18 W
+Mos    Mos is a municipality in Galicia, Spain in the province of Pontevedra.  42 11 39.08 N   8 39 11.19 W
 Mosteiro de San Clodio de Leiro                42 22 02.61 N   8 06 54.12 W
 Mosteiro de San Salvador de Celanova           42 09 06.55 N   7 57 24.85 W
 Mosteiro de Santa María de Aciveiro           42 37 03 N      8 18 06 W
 Mońki Railway station 53 24 00 N      22 47 00 E
 Mugardos       Mugardos is a municipality in the province A Coruña, Galicia, Spain.   43 27 43.7 N    8 15 12.52 W
+Muntic Muntic is a village in Istria, Croatia. 44 55 00 N      13 56 00 E
+Murol          45 34 23 N      2 56 35 E
 Muros          42 46 28.69 N   9 03 23.75 W
 Murray House           22 13 05.15 N   114 12 34.96 E
 Murviel-lès-Béziers          43 26 29 N      3 08 42 E
@@ -366,13 +509,16 @@ Museo Civico d'Arte Antica di Torino      Il Museo Civico d'Arte Antica è un polo mu
 Museo della Storia del Genoa           44 24 37.8 N    8 56 11.39 E
 Museo Regionale della Fauna Alpina - Alpenfaunamuseum "Beck-Peccoz"    Un museo sulla fauna alpina situato a Gressoney-Saint-Jean, in Valle d'Aosta, Italia.   45 46 35.57 N   7 49 36.84 E
 Museum für Moderne Kunst      The Museum für Moderne Kunst (MMK) is an art museum in Frankfurt am Main.      50 06 42.56 N   8 41 03.86 E
+Mérida (Spain)                38 54 57 N      6 20 00 W
 Möja kyrka            59 24 18.8 N    18 52 53 E
 Mössingen             48 24 23 N      9 03 27 E
-Nagaoka Tenman-gū     日本語: 長岡天満宮, 京都府長岡京市。    34 55 22.2 N    135 41 10.8 E
+Mühlen am Löbauer Wasser             51 12 02.21 N   14 39 21.09 E
 Narón Narón is a municipality in the province A Coruña, Galicia, Spain.     43 32 14.91 N   8 10 53.28 W
-Neda   Neda is a municipality in the province A Coruña, Galicia, Spain.       43 29 58.67 N   8 09 21.51 W
+Necrópole Megalítica da Lameira de Cima      Necrópole Megalítica da Lameira de Cima, freguesia de Antas, Penedono, Portugal.      40 56 09.92 N   7 20 57.52 W
+Neda   Neda is a municipality in the province A Coruña, Galicia, Spain.       43 29 58.68 N   8 09 21.51 W
+Nedroma                35 00 47.01 N   1 44 51.12 W
 Neues Rathaus München         48 08 16.07 N   11 34 33.35 E
-Neues Schloss, Stuttgart       The south front parallels the Planie and the Alte Schloss.      48 46 41 N      9 10 55 E
+Neues Schloss, Stuttgart               48 46 41 N      9 10 55 E
 New Mosque in Istanbul New Mosque (Yeni Cami) in Istanbul      41 01 01.05 N   28 58 19.2 E
 Niujie Mosque  The Niujie Mosque (Chinese: 牛街清真寺; pinyin: niújiē qīngzhēnsì) is the oldest mosque in Beijing, China. It was built in 996 and completely rebuilt under the Kangxi Emperor (1622-1722).   39 53 04 N      116 21 29 E
 Nizza-Ufer     The Nizza bank is a park with mild microclimate at the Main river bank in Frankfurt am Main. The Park was constructed in 1860.  50 06 15.68 N   8 40 14.37 E
@@ -380,12 +526,12 @@ Noorderplantsoen  The Noorderplantsoen is a park in the Dutch city of Groningen.
 Nullamunjie Olive Grove, Victoria      Nullamunjie olive groves are situated in the mountains of eastern Victoria, Australia on the slopes of Mount Stawell and near the banks of the Tambo River.     37 11 05 S      147 43 58 E
 O Barco de Valdeorras          42 24 38.82 N   6 58 33.51 W
 O Bolo O Bolo is a municipality in Galicia, in the province of Ourense.        42 18 27.11 N   7 05 52.28 W
-O Carballiño          42 25 53.18 N   8 04 38.48 W
+O Carballiño          42 25 53.18 N   8 04 38.47 W
 O Grove                42 29 34.29 N   8 52 04.9 W
 O Pereiro de Aguiar    O Pereiro de Aguiar is a municipality in Galicia, in the province of Ourense.   42 20 47.83 N   7 48 06.62 W
 O Porriño     O Porriño is a municipality in Galicia, Spain in the province of Pontevedra.   42 09 41.84 N   8 37 15 W
-O Rosal        O Rosal is a municipality in Galicia, Spain in the province of Pontevedra.      41 56 07.29 N   8 50 05.63 W
-O Ézaro, Dumbría             42 54 38.2 N    9 07 54.11 W
+O Rosal        O Rosal is a municipality in Galicia, Spain in the province of Pontevedra.      41 56 07.3 N    8 50 05.63 W
+O Ézaro, Dumbría             42 54 38.2 N    9 07 54.12 W
 Obermillstatt          46 48 36.91 N   13 35 29.41 E
 Obervellach            46 55 55.76 N   13 12 06.68 E
 Ocean Park, Hong Kong          22 14 45.1 N    114 10 33.3 E
@@ -394,62 +540,96 @@ Oia       Oia is a municipality in Galicia, Spain in the province of Pontevedra.  42 00
 Oleiros        Oleiros is a municipality in the province A Coruña, Galicia, Spain.    43 19 57.16 N   8 18 54.38 W
 Oleiros, Ribeira               42 36 11.29 N   9 00 20.52 W
 Olgastraße (Stuttgart)                48 46 04.17 N   9 10 44.23 E
+Oradour-sur-Glane      Oradour-sur-Glane was a village in the Limousin region of Vichy France that came under direct German control in 1942.   45 55 55 N      1 01 54 E
+Oran           35 41 49 N      0 37 59 W
 Orangerie (Neustrelitz)        Die Orangerie in Neustrelitz wurde bereits 1755 erbaut und erfuhr 1840 bis 1842 einen grundlegenden Umbau durch Friedrich Wilhelm Buttel.       53 21 40 N      13 03 27 E
 Organización Médica Colegial de España      Spanish Medical Colleges Organization   40 24 57.24 N   3 41 49.49 W
 Ortigueira     Ortigueira is a municipality in the province A Coruña, Galicia, Spain. 43 41 12.94 N   7 51 10.47 W
-Os Blancos     Os Blancos is a municipality in Galicia, in the province of Ourense.    41 59 50.61 N   7 45 12.35 W
+Os Blancos     Os Blancos is a municipality in Galicia, in the province of Ourense.    41 59 50.61 N   7 45 12.34 W
 Ouaisné               49 10 45.75 N   2 11 10.77 W
 Oza-Cesuras    Oza-Cesuras, municipality in the province of A Coruña, in Galicia (Spain).     43 16 44.24 N   8 12 55.95 W
-Ozurgeti       Ozurgeti is a town and the regional administrative centre of Western Georgian province of Guria, former Macharadze or Makharadze (named in honor of Filipp Makaradze).  41 55 26.26 N   42 00 33.84 E
+Ozurgeti       Ozurgeti is a town and the regional administrative centre of Western Georgian province of Guria, former Macharadze or Makharadze.       41 55 26.26 N   42 00 33.84 E
 Oímbra                41 53 07.94 N   7 28 19.78 W
-Palacete de Belomonte  Palacete de Belomonte / Palácio de Belomonte / Casa dos Pacheco Pereira. Em Portugal, Porto, Porto, São Nicolau.      41 08 33.18 N   8 36 58.35 W
+Palace and park of Versailles          48 48 15.85 N   2 07 23.38 E
+Palais Brongniart      The Palais Brongniart was the site of the Paris Stock Exchange until 1987.      48 52 09.01 N   2 20 26.98 E
+Palais de justice de Paris     Palais de Justice is a building complex on the Île-de-la-Cité in Paris. It was built on the site of the Palais de la Cité, the residence of the Kings of France until the 14th century.      48 51 20.6 N    2 20 42.18 E
+Palais de l'Élysée           48 52 13 N      2 18 59 E
+Palais des Papes       The Palais des Papes in Avignon, France was the residency of popes during the 14th and 15th century.       43 57 02 N 4 48 25 E   43.95068 N 4.80704 E  43.95068 4.80704
+Palais du parlement de Bretagne                48 06 46.08 N   1 40 40.01 W
 Palais du parlement du Dauphiné               45 11 35.97 N   5 43 42.86 E
+Palais du Tau          49 15 11 N      4 02 04 E
+Palais ducal de Nevers The Palais Ducal in Nevers, France      46 59 18 N      3 09 30 E
 Palais Garnier The Palais Garnier, also known as Opéra Garnier or Opéra national de Paris is an opera house situated at the northern end of the avenue de l'Opera, in Paris. 48 52 19 N      2 19 54 E
+Palais Granvelle (Besançon)           47 14 08.52 N   6 01 35.76 E
+Palais Idéal          45 15 22.85 N   5 01 42.74 E
+Palais Longchamp               43 18 15.48 N   5 23 40.2 E
+Palais Royal           48 51 52.5 N    2 20 15.1 E
 Palmenhaus, Vienna     Vienna's Palmenhaus ('palm house', a Jugendstil greenhouse built in 1901) is a building in Vienna's 1st district, beneath 'Burggarten' and Hofburg.     48 12 18 N      16 22 01 E
 Palácio de Estói             37 05 47.79 N   7 53 44.05 W
 Palácio Nacional da Pena              38 47 15.24 N   9 23 25.75 W
 Palácio Nacional de Mafra     O Palácio Nacional de Mafra é um palácio e mosteiro monumental de estilo neoclássico localizado em Mafra (Portugal) a poucos mais de 50 quilómetros de Lisboa.     38 56 12 N      9 19 35 W
 Pamukkale      Pamukkale is a natural site and attraction and a UNESCO World Heritage Site in south-western Turkey.    37 55 25 N      29 07 24 E
+Panthéon de Paris             48 50 46 N      2 20 45 E
+Parc Astérix          48 27 21 N      4 35 24 E
 Parc du Thabor         48 06 51 N      1 40 12 W
+Parc Monceau           48 52 45.84 N   2 18 33.23 E
+Parc Natural del Delta de l'Ebre       Village 40 42 09 N      0 48 32 E
+Paris-Gare de Lyon             48 50 41 N      2 22 25 E
+Emin Pasha     Emin Pasha (born Eduard Schnitzer, 1840-1892) was a German explorer of Central Africa.  42 12 40.68 N   20 44 28.26 E
+Petit Palais   The Petit Palais is a palace in Paris, France built for the Universal Exhibition in 1900. It now houses the Musée des Beaux-Arts de la Ville de Paris (Paris Museum of Fine Arts).     48 51 57.72 N   2 18 52.39 E
 Petuelpark             48 10 39.64 N   11 34 45.32 E
 Petuelring     The Petuelring is a street in Munich, part of the Mittlerer Ring around the city centre.        48 10 40.25 N   11 34 19 E
 Petueltunnel   The Petueltunnel is a tunnel that runs beneath the Petuelring between the Boroughs Milbertshofen-Am Hart and Schwabing-West in Munich.  48 10 39.9 N    11 34 37.48 E
+Phare de Chassiron             46 02 48.12 N   1 24 37.01 W
 Philadelphia City Hall Philadelphia City Hall is the seat of government for the city of Philadelphia, Pennsylvania.    39 57 08.28 N   75 09 48.96 W
 Piazza Corvetto (Genova)       Piazza Corvetto è una delle principali piazze di Genova        44 24 36.1 N    8 56 18.2 E
 Piazza della Vittoria (Genoa)  Piazza della Vittoria è una delle principali piazze di Genova. 44 24 11.12 N   8 56 42.45 E
-Piazza Navona  Piazza Navona is a square in Parione, the VI. Rione (district) from Rome / Italy / EU.  41 53 56.21 N   12 28 23.15 E
+Piazza delle Erbe (Verona)             45 26 36.74 N   10 59 48.53 E
+Piazza Navona  Piazza Navona is a square in Parione, the VI. Rione (district) from Rome, Italy.        41 53 56.21 N   12 28 23.15 E
 Piazza Venezia Piazza Venezia is Located in Rome.      41 53 47 N      12 28 57 E
 Pieve di San Giorgio di Valpolicella   The Pieve di San Giorgio di Valpolicella is a particularly large and elaborate Pieve (ancient church) of the city of Sant'Ambrogio di Valpolicella, Italy.      45 32 07 N      10 51 00 E
 Piode  Piode è un comune della Valsesia, in provincia di Vercelli, Piemonte, Italia.  45 46 09.98 N   8 02 57.28 E
-Piñor Piñor is a municipality in Galicia, in the province of Ourense.        42 29 50.7 N    8 00 21.4 W
-Place royale du Peyrou         43 36 40 N      3 52 15 E
+Piñor Piñor is a municipality in Galicia, in the province of Ourense.        42 29 50.69 N   8 00 21.39 W
+Place de la Concorde   The Place de la Concorde is one of the major public squares in Paris, France.   48 51 56 N      2 19 16 E
+Place des Vosges       Place des Vosges in Paris, France       48 51 20 N      2 21 56 E
 Plaridel Airport               14 53 27.91 N   120 51 09.9 E
 Plaça Catalunya       Plaça Catalunya (or Plaza de Cataluña in spanish) is a large square in central Barcelona.     41 23 13.21 N   2 10 12.09 E
-Ponte de Rande         42 17 18.66 N   8 39 37.45 W
+Pont Ambroix   The Pont Ambroix was a Roman bridge across the Vidourle in Gallargues-le-Montueux, Gard department, and Villetelle, Hérault department, both Languedoc-Roussillon, France.     43 43 02 N      4 09 07 E
+Pont Camille-de-Hogues         46 48 49.12 N   0 32 15.32 E
+Pont de la Concorde            48 51 47.99 N   2 19 09.98 E
+Pont du Gard   The Pont du Gard is a Roman aqueduct bridge near Nîmes, France.        43 56 50.28 N   4 32 07.8 E
+Pont Flavien   The Pont Flavien is a Roman bridge in Saint-Chamas, Bouches-du-Rhône department, Provence-Alpes-Côte d'Azur, France.  43 32 29 N      5 02 35 E
+Pont Julien    The Pont Julien is a Roman bridge near Bonnieux, Vaucluse department, Provence-Alpes-Côte d’Azur, France.    43 51 45 N      5 18 28 E
+Pont Neuf              48 51 26.81 N   2 20 29.82 E
+Pont sur la Laye       Roadway 43 55 48 N      5 45 23 E
+Ponte de Rande The Rande Bridge is a cable-stayed bridge near Vigo, Galicia, Spain.    42 17 18.66 N   8 39 37.45 W
 Ponte Sant'Angelo (Rome)       The Ponte Sant'Angelo is a Roman bridge across the Tiber in Rome, Italy.        41 54 06.46 N   12 27 59.24 E
 Pontedeume             43 24 13.68 N   8 09 46.85 W
-Pontevedra             42 25 50.84 N   8 38 56.28 W
+Pontevedra             42 25 50.84 N   8 38 56.27 W
 Pordoi Pass    The Pordoi Pass is an Alpine pass in the Dolomites.     46 29 19.33 N   11 48 52.54 E
 Port of Kobe   Port of Kobe in Kobe, Hyōgo Prefecture, Japan  34 40 39.17 N   135 13 36.97 E
 Porta Pia              41 54 33 N      12 30 04 E
 Porta san Sebastiano           41 52 25 N      12 30 07 E
 Porta Soprana (Genova) Porta Soprana a Genova. 44 24 19.76 N   8 56 05.6 E
 Porta Westfalica               52 14 22.66 N   8 55 14.31 E
-Porto antico di Genova         44 24 34.22 N   8 55 04.27 E
+Porto antico di Genova         44 24 34.22 N   8 55 04.26 E
 Porto Azzurro          42 46 05.02 N   10 23 48.33 E
 Portor, Negreira               42 54 45.44 N   8 41 52.17 W
 Potemkin Stairs                46 29 18.75 N   30 44 31.18 E
 Praia de Cabanas               43 24 55.21 N   8 10 23.62 W
 Prinzenbau Stuttgart           48 46 38.55 N   9 10 40.96 E
+Promenade du Peyrou            43 36 40 N      3 52 15 E
+Prytanée national militaire           47 42 10.09 N   0 04 35.77 W
+Puerto de Navacerrada          40 47 19.04 N   4 00 13.23 W
 Puits Arthur-de-Buyer          47 40 37.23 N   6 36 51.29 E
 Punta Manara   Manara Bivouac  44 15 19.08 N   9 24 20.88 E
-Punxín        Punxín is a municipality in Galicia, in the province of Ourense.       42 22 06.15 N   8 00 03.17 W
+Punxín        Punxín is a municipality in Galicia, in the province of Ourense.       42 22 06.15 N   8 00 03.16 W
 Quartier de Beaugrenelle               48 51 03.87 N   2 17 07.61 E
-Rairiz de Veiga                42 04 57.65 N   7 49 56.93 W
+Rairiz de Veiga                42 04 57.65 N   7 49 56.92 W
 Ravensberger Bahn      Die Ravensberger Bahn ist eine Eisenbahnstrecke von Bielefeld nach Rahden.      52 13 36.48 N   8 31 20.75 E
-Redondela      Redondela is a municipality in Galicia, Spain in the province of Pontevedra.    42 17 03 N      8 36 31.13 W
 Regattastrecke Oberschleißheim        Die Regattastrecke Oberschleißheim ist ein künstlicher, rechteckiger Grundwassersee im Norden von München, angelegt für die Olympischen Sommerspiele 1972.  48 14 33.67 N   11 30 54.38 E
 Reggia di Caserta      La Reggia di Caserta, Palazzo Reale, è stata la dimora della dinastia dei Borboni, sovrani del Regno delle due Sicilie. È situata a Caserta, in Campania (Italia).    41 04 26.27 N   14 19 36.92 E
+Reservoir in Stara Morawa      Reservoir in Stara Morawa near Stronie Śląskie (Lower Silesian Voivodeship, Poland)   50 16 31.15 N   16 52 47.84 E
 Rianxo Rianxo is a port town in Galicia, Spain, in the province of A Coruña.  42 38 38.62 N   8 48 44.67 W
 Ribadavia              42 17 13.06 N   8 08 34.54 W
 Ribadumia      Ribadumia is a municipality in Galicia, Spain in the province of Pontevedra.    42 30 50.38 N   8 45 25.68 W
@@ -460,91 +640,105 @@ Rio Marina       Rio Marina is a village in Isola d'Elba Toscana in Italy        42 48 44.2 N
 Risiera di San Sabba   La Risiera di San Sabba è stato un campo di concentramento nazista, attivo negli ultimi anni della seconda guerra mondiale a Trieste, Italia.  45 37 26.09 N   13 47 22.1 E
 Riós  Riós is a municipality in Galicia, in the province of Ourense. 41 58 29.53 N   7 16 55.68 W
 Robben Island  Robben Island, Cape Town, South Africa  33 48 24.24 S   18 21 58.4 E
-Rocca Grimalda Rocca Grimalda è un comune dell'Alto Monferrato, in provincia di Alessandria, Piemonte, Italia.        44 40 17.48 N   8 38 55.12 E
-Rodeiro        Rodeiro is a municipality in Galicia, Spain in the province of Pontevedra.      42 39 00.44 N   7 56 59.93 W
+Rocca Grimalda Rocca Grimalda è un comune dell'Alto Monferrato, in provincia di Alessandria, Piemonte, Italia.        44 40 17.48 N   8 38 55.11 E
+Rodeiro        Rodeiro is a municipality in Galicia, Spain in the province of Pontevedra.      42 39 00.44 N   7 56 59.94 W
+Roman Bridge (Saint-Thibéry)  Old mill nearby 43 23 34.39 N   3 25 58.21 E
+Roman Bridge (Vaison-la-Romaine)       The Roman Bridge at Vaison-la-Romaine is a bridge across the Ouvèze in the Vaucluse department, Provence, France.      44 14 20.3 N    5 04 28.7 E
 Roman Theatre of Catania               37 30 10.4 N    15 05 00.9 E
 Roujan         43 30 32.5 N    3 17 15.15 E
 Río Tambre            42 54 20.32 N   8 41 43.21 W
 Sacrario Militare di Redipuglia                45 51 05.58 N   13 29 22.49 E
 Sada   Sada is a municipality in the province A Coruña, Galicia, Spain.       43 21 01.43 N   8 15 16.23 W
-Safranbolu     Safranbolu is a city and district of Karabük Province, Turkey. Safranbolu is one of the World Heritage sites in Turkey.        41 14 41 N      32 41 37 E
+Safranbolu     Safranbolu is a city and World Heritage site of Karabük Province, Turkey.      41 14 41 N      32 41 37 E
 Sagrada Família               41 24 12.82 N   2 10 27.64 E
+Église Saint-Gervais-Saint-Protais            48 51 19.8 N    2 21 16.6 E
 Saint-Michel de Nahuze Prieuré du XIe siècle, situé sur la commune de Lagrasse (département de l'Aude), dont les ruines ont été inscrites comme monument historique      43 07 56.82 N   2 37 06.38 E
 Saint-Nazaire-de-Ladarez               43 30 37 N      3 04 36 E
+Sainte-Chapelle                48 51 20 N      2 20 41 E
+Château de Saissac    The Château de Saissac is a Cathar castle in the Saissac commune, Aude département of France. 43 21 25 N      2 10 04.7 E
 Salt Lake Temple       The Salt Lake Temple, the sixth temple built by the church overall, and the fourth operating temple, is the largest and best-known temple of The Church of Jesus Christ of Latter-day Saints.   40 46 14.45 N   111 53 31.18 W
-San Amaro      San Amaro is a municipality in Galicia, in the province of Ourense.     42 22 20.07 N   8 04 23.64 W
+San Amaro      San Amaro is a municipality in Galicia, in the province of Ourense.     42 22 20.07 N   8 04 23.65 W
 San Clodio, Leiro              42 22 02.61 N   8 06 54.12 W
 San Cristovo de Cea    San Cristovo de Cea is a municipality in Galicia, in the province of Ourense.   42 28 33.22 N   7 59 07.24 W
 San Giorgio in Lemine  San Giorgio in Lemine is a church in the comune of Almenno San Salvatore, Bergamo, Lombardy, Italy      45 44 46 N      9 35 50 E
+San Marco (Milan)              45 28 23.77 N   9 11 20 E
 San Sadurniño San Sadurniño is a municipality of Spain in the province of A Coruña, in the autonomous community of Galicia. 43 33 44.93 N   8 03 17.21 W
 Sandiás       Sandiás is a municipality in Galicia, in the province of Ourense.      42 06 38.53 N   7 45 28.41 W
 Sankt Martini          53 04 30 N      8 48 15 E
 Sant'Anastasia Sant'Anastasia è una chiesa di Roma.   41 53 17.6 N    12 29 03 E
+Santa Maria degli Scalzi (Venice)              45 26 29.4 N    12 19 19.56 E
+Santa Maria dei Servi (Padua)          45 24 16.56 N   11 52 32.63 E
 Santiago de Chile      Santiago is the capital of Chile, it is also the country's industrial and commercial center.    33 27 00 S      70 40 00 W
-Santiago de Compostela         42 52 50.24 N   8 32 39.96 W
 Sanxenxo               42 24 19.2 N    8 48 23.66 W
 Sarreaus               42 05 13.93 N   7 36 10.99 W
 Sarria         42 46 38.13 N   7 24 54.31 W
 Schallenberg           46 49 34 N      7 47 50 E
 Schenkendorfstraße    The Schenkendorfstraße is a street in Munich, part of the Mittlerer Ring around the city centre.       48 10 34.21 N   11 35 29.04 E
-Schloss Eutin  The Castle of Eutin is located in Eutin in Schleswig-Holstein. It was built onto the foundations of a medieval castle.  54 08 15.75 N   10 37 13.22 E
+Schloss Eutin  The Castle of Eutin is located in Eutin in Schleswig-Holstein.  54 08 15.75 N   10 37 13.22 E
 Schloss Hüffe Schloss Hüffe ist ein Ende des 13. Jahrhunderts errichtetes Wasserschloss in der Ortschaft Lashorst der Stadt Preußisch Oldendorf im Kreis Minden-Lübbecke.  52 20 05.99 N   8 30 55.72 E
 Schloss Kalkum Schloss Kalkum is a water castle in Kalkum in the north of Düsseldorf, about two kilometers northeast of Kaiserwerth.  51 18 14.9 N    6 45 26.7 E
-Schloss Nymphenburg    Nymphenburg Palace is a château in Munich. Its construction was started during the 17th century by the Prince-elector Ferdinand Maria of Bavaria.      48 09 29 N      11 30 13 E
+Schloss Nymphenburg    Nymphenburg Palace is a château in Munich.     48 09 29 N      11 30 13 E
 Schloss Petershagen    Das Schloss Petershagen in Petershagen ist ein Wasserschloss im Stil der Weserrenaissance.      52 22 46.16 N   8 58 18.62 E
 Schloss Rosenstein             48 48 15 N 9 11 30 E
 Schlosskirche (Neustrelitz)    Die Neustrelitzer Schlosskirche wurde durch Friedrich Wilhelm Buttel erbaut und ist dessen Hauptwerk in der Stadt.      53 21 35 N      13 03 32 E
 Schlossplatz Stuttgart         48 46 42.81 N   9 10 47.45 E
+Serres d'Auteuil       The Jardin des serres d'Auteuil is a botanical garden in the Bois de Boulogne, Paris 16th, located at 3 avenue de la Porte d'Auteuil and 1 avenue Gordon-Bennett.         48 50 49 N 2 15 8 E
 Sestri Levante Sestri Levante is a town in Liguria in Italy.   44 16 17.76 N   9 23 33 E
 Shokeda        Shokeda is a religious settlement in Israel, in north-west part of Negev, south to Sderot and east to the Gaza Strip.   31 25 19.56 N   34 31 28.2 E
-Sigüeiro, A Barciela, Oroso           42 58 06.42 N   8 26 33.38 W
+Sidi Okba              34 45 00 N      5 54 00 E
+Sigüeiro, A Barciela, Oroso           42 58 06.42 N   8 26 33.39 W
 Silleda                42 41 49.46 N   8 14 50.28 W
 Singapore      Singapore is a city state at the southern tip of peninsular Malaysia. It is an island approximately 40 by 20 km in size inhabited by more than five million people.     1 18 00 N       103 48 00 E
 Singapore Zoo          1 24 15.9 N     103 47 28.1 E
 Sistiana       Sistiana is a town near Trieste.        45 46 09.98 N   13 38 01.98 E
 Sistine Chapel The Sistine Chapel is located in the Vatican and is decorated with frescoes by Michelangelo.    41 54 11 N      12 27 16 E
 Sohland am Rotstein    Sohland am Rotstein is a municipality in Saxony, Germany.       51 07 00 N      14 47 00 E
-Sopron Sopron (pronounced "shop-ron"; German: Ödenburg) is a city in Hungary near the Austrian border. It is located a short train ride from Vienna.  47 41 12 N      16 34 49 E
+Sopron Sopron (pronounced "shop-ron"; German: Ödenburg) is a city in Hungary near the Austrian border.        47 41 12 N      16 34 49 E
 Spenge Spenge is a northrhine-westphalian town in the administrative district Kreis Herford.   52 08 33.29 N   8 28 59.9 E
 Spišský hrad The ruins of Spiš Castle are situated above the town of Spišské Podhradie and the village of Žehra in the Spiš region in eastern Slovakia. 48 59 58.5 N    20 46 03.3 E
 St. Jürgenskirche (Lilienthal)        The church of St. Jürgen is in the same named district of Lilienthal in the county of Osterholz in Lower Saxony, Germany.      53 10 36 N      8 48 29 E
 St. Maria in der Kupfergasse   St. Maria in der Kupfergasse is a baroque church in Cologne.    50 56 23.2 N    6 57 01.04 E
 Stadtplatz (Steyr)             48 02 20.42 N   14 25 08.89 E
 Stalis         35 17 47.4 N    25 25 25.9 E
+Stefanskyrkan, Stockholm               59 20 51.38 N   18 03 16.7 E
 Steigfriedhof  The Steigfriedhof is a cemetery in the Stadtbezirk Bad Cannstatt in Stuttgart.  48 48 41.9 N    9 12 28.95 E
 Stift Göttweig        Göttweig Abbey is a Benedictine monastery in Lower Austria.    48 22 00.2 N    15 36 45.5 E
+Stockholms stadshus            59 19 38.57 N   18 03 15.67 E
+Stockholms universitet Stockholm University, founded 1878, with about 37,000 students. 59 21 46.68 N   18 03 31.4 E
 Städtisches Lapidarium Stuttgart              48 46 03.36 N   9 10 04.58 E
-Sultanahmet Camii      The Sultan Ahmed Mosque (in Turkish Sultanahmet Camii, in English commonly called the Blue Mosque) is a mosque in Istanbul. It is regarded as one of the greatest masterpieces of Islamic architecture. 41 00 19.3 N    28 58 36.6 E
+Sultanahmet Camii      The Sultan Ahmed Mosque (in Turkish Sultanahmet Camii, in English commonly called the Blue Mosque) is a mosque in Istanbul.     41 00 19.3 N    28 58 36.6 E
 Synchrotron Soleil             48 42 33 N      2 08 41 E
 Süleymaniye camii     The Mosque of Suleiman I in Istanbul.   41 00 58.3 N    28 57 50 E
 Tarancón      Tarancón village and municipality in the province of Cuenca, part of the autonomous community of Castile-La Mancha, Spain.     40 00 45.89 N   3 00 14.85 W
 Teixido, Cedeira               43 42 36.72 N   7 59 00.44 W
-Villa romana de Tejada The ancient roman villa of La Tejeda is an archaeological site from II to V centuries in Quintanilla de la Cueza, Cervatos de la Cueza (Palencia, Spain).       40 58 36 N      4 48 25 W
-Temple Neuf (Strasbourg)               48 35 00 N      7 44 54 E
+Villa romana de Tejada The ancient roman villa of La Tejada is an archaeological site from II to V centuries in Quintanilla de la Cueza, Cervatos de la Cueza (Palencia, Spain).       40 58 36 N      4 48 25 W
 Templer Cemetery, Jerusalem    Jerusalem, German Colony, Emek Refaim street 39 31 45 47.46 N   35 13 08.68 E
 Texas State Capitol    The Texas State Capitol, located in Downtown Austin, Texas, is the fourth building to serve as the seat of Texas government.    30 16 29 N      97 44 26 W
 The Dormition Cathedral in Odessa              46 28 31.07 N   30 43 55.42 E
 The Spice Bazaar, Istanbul             41 01 00.7 N    28 58 15.13 E
+Théâtre municipal de Besançon               47 14 03.84 N   6 01 33.89 E
 Tiberias               32 47 20.04 N   35 31 20.28 E
-Tomiño        Tomiño is a municipality in Galicia, Spain in the province of Pontevedra.      41 59 31.78 N   8 44 32.53 W
+Toblinger Knoten       The Toblinger Knoten is a mountain in the Sexten Dolomites in South Tyrol.      46 38 31 N      12 18 29 E
+Tomiño        Tomiño is a municipality in Galicia, Spain in the province of Pontevedra.      41 59 31.78 N   8 44 32.54 W
 Toosa  Toosa is a village in Punjab.   30 44 45.31 N   75 41 14.53 E
 Topkapı Sarayı       İznik tiles: camp of the Mount Arafat  41 00 45.7 N    28 59 03.25 E
 Torre de San Sadurniño                42 30 25 N      8 49 16 W
 Torre de Vilanova dos Infantes         42 09 58.36 N   7 57 16.62 W
 Torres de Altamira             42 52 39.02 N   8 41 15.17 W
-Torres do Oeste                42 40 35.81 N   8 43 32.45 W
+Torres de Oeste                42 40 35.81 N   8 43 32.46 W
 Torreón dos Andrade           43 24 27.98 N   8 10 17.88 W
-Transfiguration Cathedral in Odessa            46 28 59.44 N   30 43 51.75 E
+Toshkent               41 18 00 N      69 16 00 E
+Tour Eiffel            48 51 30 N      2 17 39 E
+Tour Goguin            46 59 03.84 N   3 09 19.91 E
+Transfiguration Cathedral in Odessa    Odessa Cathedral of God's Transfiguration       46 28 59.44 N   30 43 51.75 E
 Trappentreustraße     The Trappentreustraße is a street in Munich, part of the Mittlerer Ring around the city centre.        48 08 12.34 N   11 32 03.84 E
-Trappentreutunnel      The Trappentreutunnel is a tunnel that runs beneath the Trappentreustraße in the borough Schwanthalerhöhe in Munich.  48 08 08.78 N   11 32 03.17 E
+Trappentreutunnel      The Trappentreutunnel is a tunnel that runs beneath the Trappentreustraße in the Borough Schwanthalerhöhe in Munich.  48 08 08.78 N   11 32 03.17 E
 Trastevere     Restaurant in Via della Lungaretta      41 53 22.54 N   12 28 12.76 E
 Travemünde    Travemünde is a part of Lübeck in Germany at the Baltic Sea.  53 58 00 N      10 52 00 E
-Treuners Altstadtmodell        The Treuner brothers' Altstadtmodell is a scale model which shows the oldtown of Frankfurt am Main prior to the air-raid damages in 1943 and 1944. The ruin model shows the destroyed old town of Frankfurt in spring 1945.     50 06 35 N      8 40 57 E
-Triumphal arch Barcelona               41 23 27 N      2 10 50 E
+Trinitatisfriedhof, Dresden    Cemetery „Trinitatisfriedhof“ in Dresden-Johannstadt        51 03 15.08 N   13 46 20.93 E
 Twierdza Osowiec       Dry moat around the fort No 3   53 27 44 N      22 37 38 E
 Třebechovice pod Orebem       Třebechovice pod Orebem is a town in Czech Republic in Hradec Králové Region 50 12 07 N      15 59 38 E
 Uetersen               53 41 14 N      9 40 09 E
-Universidad de Alcalá         40 28 58.53 N   3 21 47.27 W
 Université Lille Nord de France               50 36 33.38 N   3 08 29.72 E
 Upper Darby, Pennsylvania              39 57 40.2 N    75 15 32.06 W
 Urgnano                45 35 50 N      9 41 42 E
@@ -554,10 +748,10 @@ Valparaíso               33 02 45.4 S    71 36 58.9 W
 Varallo        Varallo is a town in Valsesia, Piedmont, Italy, named also Varallo Sesia.       45 49 10.95 N   8 15 19.01 E
 Varigotti      Varigotti è una frazione del comune di Finale Ligure, in provincia di Savona (riviera ligure di ponente, Liguria, Italia).     44 10 53.83 N   8 23 45.56 E
 Verducido, Pontevedra          42 28 35.96 N   8 37 06.66 W
-Verín Verín is a municipality in Galicia, in the province of Ourense.        41 56 22.46 N   7 26 19.59 W
-Via dell'Amore (Cinque Terre)  La Via dell'Amore è una passeggiata turistica di circa un chilometro che, costeggiando il mare, congiunge le località di Riomaggiore e Manarola, nelle Cinque Terre, in Liguria (Italia).     44 06 06.07 N   9 44 00.37 E
-Via Dolorosa signs             31 46 46.48 N   35 13 56.63 E
-Viana do Bolo          42 10 46.42 N   7 06 44.8 W
+Verín Verín is a municipality in Galicia, in the province of Ourense.        41 56 22.47 N   7 26 19.59 W
+Via dell'Amore (Cinque Terre)  The Via dell'Amore (=way of love) is a hiking path above the sea linking the villages of Riomaggiore and Manarola in the Cinque Terre (Liguria, Italia).        44 06 06.07 N   9 44 00.37 E
+Viana do Bolo          42 10 46.42 N   7 06 44.81 W
+Vieilles prisons d'Annecy      Ancient Jails or the Palais de l'Île of Annecy.        45 53 54.89 N   6 07 36.59 E
 Vigo   Vigo is a Spanish municipality and the largest city in Galicia. 42 14 00.05 N   8 42 37.59 W
 Vilagarcía de Arousa          42 35 33.08 N   8 46 36.13 W
 Vilamarín     Vilamarín is a municipality in Galicia, in the province of Ourense.    42 27 51.29 N   7 53 23.64 W
@@ -565,58 +759,168 @@ Vilanova de Arousa       Vilanova de Arousa is a municipality in Galicia, Spain in the
 Vilar de Santos        Vilar de Santos is a municipality in Galicia, in the province of Ourense.       42 05 10.72 N   7 47 44.37 W
 Vilardevós            41 54 25.21 N   7 18 45.37 W
 Vilariño de Conso             42 09 56.5 N    7 11 02.5 W
-Vilasobroso, Mondariz          42 12 17.3 N    8 27 20.51 W
+Vilasobroso, Mondariz          42 12 17.31 N   8 27 20.51 W
 Vilassar de Mar        Vilassar de Mar is a village in the county of the Maresme, Catalonia.   41 30 19 N      2 23 34 E
 Villa Berg             48 47 31.34 N   9 12 27.17 E
 Villa d'Este (Tivoli)  The Villa d'Este in Tivoli is a masterpiece of Italian architecture and garden design.  41 57 45 N      12 47 46 E
+Villa Ephrussi de Rothschild           43 41 48 N      7 19 42.5 E
+Villa Les Glycines               48 40 46 N 6 10 56 E
+Villa Majorelle                  48 41 8 N 6 9 50 E
 Villa Schneider        Villa Schneider è un palazzo storico di Biella (Piemonte, Italia), servito come quartier generale delle SS durante la seconda guerra mondiale. 45 33 49.52 N   8 03 04.87 E
 Village Vanguard       The Village Vanguard is a jazzclub in Greenwich Village (New York City).        40 44 09.64 N   74 00 05.81 W
 Villeneuve-d'Ascq      Villeneuve-d'Ascq is a commune within Lille Metropolis, in northern France      50 37 24 N      3 08 42 E
-Wakayama prefecture    Wakayama Prefecture is located in the Kinki Region of the island of Honshu, Japan.      34 13 33.7 N    135 10 03 E
 Walhalla, Victoria             37 56 37.43 S   146 27 02.75 E
 Walt Disney Concert Hall               34 03 19.36 N   118 14 59.61 W
 Wayside Cross Villenstraße, Bonn-Dottendorf           50 42 15.73 N   7 06 43.18 E
 Weckschnapp            50 56 57.48 N   6 57 55.5 E
 West Lake              30 15 00 N      120 09 00 E
 Whitemarsh Hall                40 04 42 N      75 07 44 W
-Wild fire area at lake Norra Hörken   Norra Hörken is a lake situated between Ludvika and Ljusnarsberg municipalities in central Sweden. This gallery page shows some ecological aspects of a small wildfire in a rather typical Scandinavian forest.        60 02 30 N      14 54 37 E
-Wilhelmsstift Tübingen                48 31 16.19 N   9 03 18.52 E
+Wilhelmsstift Tübingen                48 31 16.19 N   9 03 18.53 E
 Woodlawn Cemetery (Bronx)      Gate on Jerome Avenue   40 53 30 N      73 52 12 W
 Xinzo de Limia Xinzo de Limia is a municipality in Galicia, in the province of Ourense.        42 03 36 N      7 43 19.87 W
-Yad Vashem     The memorial of the murdered jews of Suwałki and the neighbour communities, Yad Vashem, Jerusalem      31 46 23.55 N   35 10 29.2 E
+Xàbia Xàbia is a village in the province of Alicante.        38 47 21 N      0 09 47 E
+Yad Vashem             31 46 23.55 N   35 10 29.2 E
 Yao    Yao (八尾市, Yao-shi) is a city in Osaka, Japan.     34 37 36.9 N    135 36 03 E
-Zwangsarbeitslager Rees-Groin und Bienen               51 46 44.76 N   6 24 25.92 E
-Zwehrenturm    The Zwehrenturm from 1330 is a remain of Kassel's medieval defensive wall. Temporary it contained an observatory.       51 18 49 N      9 29 54 E
+Zwehrenturm    The Zwehrenturm from 1330 is a remain of Kassel's medieval defensive wall.      51 18 49 N      9 29 54 E
+Äußerer Plauenscher Friedhof Cemetery „Äußerer Plauenscher Friedhof“ in Dresden-Plauen 51 01 11.89 N   13 42 26.03 E
 École centrale de Lille       École Centrale de Lille is a graduate engineering school located in campus Lille I within Université Lille Nord de France.    50 36 21.62 N   3 08 13.63 E
+Église de l'Assomption simultanée (La Petite-Pierre)         48 51 25.2 N    7 18 55.84 E
+Église de la Sainte-Trinité (Lauterbourg)            48 58 30.1 N    8 10 40.77 E
+Église de Saint-Lothain               46 49 27.84 N   5 38 30.12 E
 Église de Saint-Paul de Frontignan            43 26 50.47 N   3 45 18.66 E
+Église des Augustins (Ribeauvillé)           48 11 43.44 N   7 19 08.87 E
 Église des Jésuites (Molsheim)               48 32 25.3 N    7 29 45 E
+Église des Saints-Innocents (Blienschwiller)          48 20 25.8 N    7 25 06.17 E
 Église Notre-Dame (Guebwiller)                47 54 20.75 N   7 12 52.56 E
+Église Notre-Dame de la Dalbade               43 35 51.36 N   1 26 32.89 E
+Église Notre-Dame-de-l'Assomption (Bergheim)          48 12 18 N      7 21 53.28 E
+Église Notre-Dame-de-l'Assomption (Bernardswiller)            48 27 10.08 N   7 27 49.57 E
+Église Notre-Dame-de-l'Assomption (Monswiller)                48 45 17.61 N   7 22 39.66 E
+Église Notre-Dame-de-l'Assomption (Rosenwiller)               48 30 21.96 N   7 26 25.48 E
+Église Notre-Dame-de-l'Assomption (Rouffach)          47 57 24 N      7 18 02 E
+Église Notre-Dame-de-la-Nativité (Saverne)           48 44 28.32 N   7 21 50.36 E
+Église protestante (Balbronn)         48 35 05.28 N   7 26 15.97 E
+Église protestante (Baldenheim)               48 14 15 N      7 32 14.35 E
+Église protestante (Berg)             48 53 52.08 N   7 09 24.01 E
+Église protestante (Bischheim)                48 36 55.08 N   7 45 21.89 E
+Église protestante (Fouday)           48 25 17.76 N   7 11 12.95 E
+Église protestante (Harskirchen)              48 56 02.04 N   7 02 20.15 E
+Église protestante (Scharrachbergheim)                48 35 35.52 N   7 29 55.9 E
+Église protestante (Schiltigheim)             48 36 21.96 N   7 45 05.04 E
+Église protestante (Weiterswiller)            48 51 10.44 N   7 24 50.9 E
+Église protestante du Temple Neuf (Strasbourg)                48 35 00 N      7 44 54 E
+Église Saint-Eustache de Paris                48 51 48 N      2 20 42 E
+Église Saint-Laurent (Paris)          48 52 29.45 N   2 21 29.92 E
 Église Saint-Michel d'Ernolsheim-lès-Saverne Bells   48 47 27.85 N   7 22 47.57 E
+Église Saint-Nicolas-du-Chardonnet    St. Nicolas du Chardonnet is a church in the centre of Paris, France located in the 5th arrondissement. 48 50 57 N      2 21 01 E
+Église Saint-Pierre de Montmartre     Saint-Pierre de Montmartre is a church in Paris 48 53 12 N      2 20 31 E
 Église Saint-Pierre-de-Rhèdes                43 35 16.22 N   3 04 43.64 E
 Église Saint-Pierre-Saint-Paul de Rueil-Malmaison             48 52 35.4 N    2 10 53.15 E
+Église Saint-Sulpice          48 51 04 N      2 20 05 E
+Église Saint-Étienne-du-Mont Saint-Étienne-du-Mont church is located in Paris, nearby Panthéon.    48 50 47 N      2 20 52 E
+Église Sainte-Marie-Madeleine de Rennes-le-Château           42 55 41.05 N   2 15 45.69 E
 Église Saints-Pierre-et-Paul (Andlau)         48 23 16.3 N    7 24 54.3 E
+Église Saints-Pierre-et-Paul (Eguisheim)              48 02 32.28 N   7 18 21.2 E
+Église Saints-Pierre-et-Paul (Hohatzenheim)           48 42 44.64 N   7 36 59.11 E
 Église Saints-Pierre-et-Paul (Neuwiller-lès-Saverne)         48 49 25 N      7 24 20 E
+Église Saints-Pierre-et-Paul (Obernai)                48 27 47.88 N   7 28 54.48 E
+Église Saints-Pierre-et-Paul (Ottmarsheim)            47 47 13.2 N    7 30 25.2 E
+Église Saints-Pierre-et-Paul (Rosheim)                48 29 48 N      7 28 14 E
+Église Saints-Pierre-et-Paul (Sigolsheim)             48 08 04.2 N    7 18 03.1 E
 Église Saints-Pierre-et-Paul (Wissembourg)            49 02 14 N      7 56 30 E
+Église St Antoine de Padoue (Saverne) The cloister    48 44 29.4 N    7 21 40.82 E
+Église St Arbogast (Offenheim)                48 37 53.76 N   7 36 59.54 E
+Église St Barthélemy (Sarrewerden)           48 55 22.8 N    7 04 56.93 E
+Église St Benoît (Bergholtzzell)             47 55 51.34 N   7 13 54.48 E
+Église St Blaise (Valff)              48 25 12.36 N   7 31 06.96 E
 Église St Cyriaque (Altorf)           48 31 22.7 N    7 31 50 E
+Église St Gall (Niedermorschwihr)             48 05 57.84 N   7 16 26.47 E
+Église St Gall protestante (Domfessel)                48 57 06.48 N   7 09 07.56 E
+Église St Georges (Châtenois)                48 16 09.12 N   7 23 51 E
+Église St Georges (Sélestat)         48 15 36 N      7 27 24.12 E
+Église St Grégoire (Ribeauvillé)            48 11 49.2 N    7 19 00.41 E
+Église St Guillaume protestante (Strasbourg)          48 34 55.5 N    7 45 28 E
+Église St Hippolyte (Saint-Hippolyte)         48 14 01.68 N   7 22 04.01 E
+Église St Jacques-le-Majeur (Kuttolsheim)             48 38 37.32 N   7 31 41.34 E
+Église St Jacques-le-Majeur simultanée (Dettwiller)          48 45 12.6 N    7 27 56.77 E
+Église St Jacques-le-Majeur simultanée (Hunawihr)            48 10 42.24 N   7 18 38.02 E
+Église St Jean (Strasbourg)           48 35 04 N      7 44 25 E
+Église St Jean protestante (Wissembourg)              49 02 18.96 N   7 56 33.36 E
 Église St Jean-Baptiste (Saint-Jean-Saverne)          48 46 18.7 N    7 21 48.5 E
+Église St Jean-Baptiste (Surbourg)            48 54 34.2 N    7 50 50.28 E
+Église St Jean-Baptiste (Wattwiller)          47 50 07.72 N   7 10 37.27 E
+Église St Jean-Baptiste simultanée (Hohwiller)               48 45 12.78 N   7 27 56.77 E
+Église St Laurent (Dieffenbach-au-Val)                48 18 44.64 N   7 19 41.34 E
+Église St Laurent protestante (Dorlisheim)            48 31 30 N      7 29 13.99 E
 Église St Léger (Guebwiller)         47 54 42.1 N    7 12 33.75 E
+Église St Léger (Murbach)            47 55 24 N      7 09 29 E
+Église St Martin (Ammerschwihr)               48 07 37.92 N   7 16 54.01 E
+Église St Martin (Ebersheim)          48 18 14.04 N   7 30 14.08 E
+Église St Martin (Pfaffenheim)                47 59 05.28 N   7 17 08.59 E
+Église St Martin protestante (Barr)           48 24 33.84 N   7 26 51.47 E
+Église St Martin protestante (Westhoffen)             48 36 01.8 N    7 26 30.3 E
 Église St Maurice (Ebersmunster)              48 18 39.5 N    7 31 37 E
+Église St Maurice (Fegersheim)                48 29 23.64 N   7 40 50.27 E
+Église St Maurice (Orschwiller)               48 14 27.24 N   7 22 44.62 E
+Église St Maurice (Soultz-Haut-Rhin)          47 53 13.2 N    7 13 48.29 E
+Église St Maurice (Soultz-les-Bains)          48 34 17.4 N    7 29 09.35 E
+Église St Maurice (Willgottheim)              48 40 14.52 N   7 30 33.23 E
+Église St Michel (Reichshoffen)               48 55 54.84 N   7 39 52.02 E
+Église St Michel (Weyersheim)         48 43 06.24 N   7 48 07.96 E
+Église St Médard (Bœrsch)           48 28 40.44 N   7 26 24.4 E
+Église St Nicolas (Haguenau)          48 49 13 N      7 47 32 E
+Église St Nicolas (Neuve-Église)             48 19 50.88 N   7 18 48.24 E
+Église St Nicolas (Wingersheim)               48 43 18.84 N   7 38 08.02 E
+Église St Pantaléon (Gueberschwihr)          48 00 16.92 N   7 16 29.78 E
+Église St Paul protestante (Strasbourg)               48 35 11 N      7 45 35 E
+Église St Pierre "Dompeter" (Molsheim, Avolsheim)             48 33 24.12 N   7 30 19.59 E
 Église St Pierre le Jeune catholique (Strasbourg)             48 35 18.35 N   7 44 55.75 E
 Église St Pierre le Jeune protestante (Strasbourg)            48 35 08 N      7 44 47 E
+Église St Rémi (Itterswiller)                48 21 51.48 N   7 25 37.42 E
+Église St Sébastien (Soultzmatt)             47 57 37.08 N   7 14 15.36 E
 Église St Thomas protestante (Strasbourg)             48 34 47 N      7 44 44 E
+Église St Trophime (Eschau)           48 29 25.08 N   7 42 57.96 E
+Église St Ulrich (Altenstadt)         49 01 49.8 N    7 58 05.88 E
+Église St Ulrich (Wittersheim)                48 46 53.04 N   7 39 27.47 E
+Église St Étienne (Rosheim)          48 29 43.8 N    7 27 59.72 E
+Église St Étienne (Seltz)            48 53 37.32 N   8 06 28.44 E
+Église St Étienne simultanée (Wangen)               48 37 01.56 N   7 27 53.68 E
+Église Ste Anne (Turckheim)           48 05 15.72 N   7 16 41.12 E
+Église Ste Aurélie protestante (Strasbourg)          48 34 53 N      7 44 00 E
+Église Ste Colombe (Hattstatt)                48 00 44.28 N   7 18 06.01 E
+Église Ste Croix (Kaysersberg)        Lamentation of Christ   48 08 20.04 N   7 15 48.56 E
+Église Ste Croix (Rountzenheim)               48 49 08.76 N   8 00 26.39 E
+Église Ste Foy (Sélestat)            48 15 33.67 N   7 27 21.81 E
+Église Ste Lucie (Niederhergheim)             47 59 10.32 N   7 23 48.41 E
 Église Ste Madeleine (Strasbourg)             48 34 48 N      7 45 17 E
+Église Ste Marguerite (Geispolsheim)          48 30 50.4 N    7 38 36.06 E
+Église Ste Odile (Lapoutroie)         48 09 08.64 N   7 10 03.97 E
+Église Ste Odile (Wintzfelden)                47 58 32.88 N   7 11 49.92 E
+Église Ste Walburge (Walbourg)                48 53 05.51 N   7 47 21.97 E
+Églises St Pierre le Vieux (Strasbourg)               48 34 58 N      7 44 24 E
 Đình Bảng  Bảng Communal House (Đình Bảng in Vietnamese) is one of largest and finest village communal houses in Việt Nam. It is located in Đình Bảng commune, Từ Sơn district, Bắc Ninh province.        21 06 29.99 N   105 57 06.31 E
+Архангельск         64 33 00 N      40 32 00 E
+Астана           51 11 00 N      71 24 00 E
+Астрахань             46 20 00 N      48 01 00 E
+Барнаул         53 21 24 N      83 47 14 E
 Боровск Borovsk 55 12 27.19 N   36 29 05.05 E
-กรุงเทพมหานคร        Bangkok 13 45 08 N      100 29 38 E
+Душанбе         38 34 23 N      68 47 11 E
+Екатеринбург               59 57 00 N      30 19 00 E
+Москва           55 45 21 N      37 37 04 E
+Нижний Новгород          56 19 37 N      44 00 27 E
+Санкт-Петербург          59 57 00 N      30 19 00 E
+Северодвинск               64 34 00 N      39 51 00 E
+กรุงเทพมหานคร        Bangkok 13 45 00 N      100 31 00 E
 სვეტიცხოველი           41 50 31 N      44 43 16 E
 ჯვარი                41 30 06.84 N   44 26 24.72 E
 鹿児島市   Kagoshima(鹿児島市; -shi) is a city in Japan and the capital city of Kagoshima Prefecture.      31 35 48.5 N    130 33 25.7 E
 薩摩川内市        Satsumasendai is a city in Kagoshima prefecture, Japan. 31 48 48.5 N    130 18 14.3 E
-上海 Shanghai is the largest city in China and is divided into 18 districts and one county (the island in the Yangtze River). It is located on the coast of eastern China at the mouth of the Chang Jiang (Yangtze River), and borders the provinces of Jiangsu and Zhejiang.        31 10 00 N      121 29 00 E
+上海 Shanghai is the largest city in China and is divided into 18 districts and one county (the island in the Yangtze River). It is located on the coast of eastern China at the mouth of the Chang Jiang (Yangtze River), and borders the provinces of Jiangsu and Zhejiang.        31 10 00 N      121 28 00 E
+东平 Daqing River    35 54 30 N      116 18 00 E
 中南海      Zhongnanhai (Chinese: 中南海; pinyin: Zhōngnánhăi) is a complex of buildings in Beijing, China which serves as the central headquarters for the Communist Party of China and the government of the People's Republic of China.    39 54 41 N      116 22 50 E
-九寨沟      Jiuzhaigou Valley (Chinese: 九寨沟; pinyin: Jiǔzhàigōu; lit. "Nine Stockades Gully") is a nature reserve in Aba(阿坝) Tibetan and Qiang Autonomous District, northern Sichuan province, China. It is known for its many multi-level waterfalls and colorful lakes, and was declared a UNESCO World Heritage Site in 1992.       33 09 34 N      103 52 40 E
+九寨沟      Jiuzhaigou Valley (Chinese: 九寨沟; pinyin: Jiǔzhàigōu; lit. "Nine Stockades Gully") is a nature reserve in Aba(阿坝) Tibetan and Qiang Autonomous District, northern Sichuan province, China.  33 09 34 N      103 52 40 E
 云南 Yunnan(云南) is a Chinese southwest border province, with the most varied nationalities in China. There are 52 nationalities of people living in Yunnan, out of 56 total throughout China.    25 03 00 N      101 52 00 E
-京都市      Kyoto is a city in Japan. It was the capital of Japan from 794 to 1869. 35 00 41.8 N    135 46 05.2 E
+京都市      Kyoto is a city in Japan. It was the capital of Japan from 794 to 1869. 35 00 42 N      135 46 05 E
 兵馬俑      Terracotta Army 34 23 05.7 N    109 16 23.1 E
 別府市      Beppu is a famous onsen city in Oita Prefecture on the island of Kyushu in Japan.       33 17 04.6 N    131 29 28.6 E
 北九州市   Kitakyūshū is a city in Fukuoka Prefecture on the island of Kyushu, Japan.    33 53 00.3 N    130 52 30.7 E
@@ -625,10 +929,12 @@ Zwehrenturm       The Zwehrenturm from 1330 is a remain of Kassel's medieval defensive
 南京 Nanjing (南京) is the capital of Jiangsu Province of China. It was the Chinese capital from 1927-1949.        32 03 00 N      118 46 00 E
 南法華寺   Minamihokke-ji is the Buddhist temple in Takatori, Nara prefecture, Japan.      34 25 35.1 N    135 48 35.5 E
 台南市              22 59 00 N      120 11 00 E
+名古屋市           35 07 00 N      136 56 00 E
 和歌山市   Wakayama (和歌山市, Wakayama-shi) is the capital city of Wakayama Prefecture in the Kansai region of Japan. 34 13 49.3 N    135 10 14.7 E
-四川 Sichuan(四川) is the most densely populated province in west China. It is famous for its peppery dishes.      30 08 00 N      102 56 00 E
+哈尔滨      Harbin is a sub-provincial city in north-east China and the capital of the Heilongjiang Province.       45 48 05 N      126 31 45 E
+四川 Nature reserve in Aba(阿坝) Tibetan and Qiang Autonomous District, northern Sichuan province, China. It is known for its many multi-level waterfalls and colorful lakes, and was declared a UNESCO World Heritage Site in 1992.       30 08 00 N      102 56 00 E
 圆明园      Yuanmingyuan (pinyin: Yuanmingyuan, 圆明园), or the old Summer Palace in Peking.     40 00 26 N      116 17 33 E
-大津市      Otsu is a city in Japan. It was the capital of Japan from 667 to 672.   35 01 04.1 N    135 51 17 E
+大津市      Ōtsu is a city in Japan. It was the capital of Japan from 667 to 672.  35 01 04.1 N    135 51 17 E
 大阪市      Osaka   34 41 37.5 N    135 30 07.6 E
 天坛 Temple of Heaven in Beijing.    39 52 56.1 N    116 24 23.7 E
 天安門      Tiananmen (Gate of Heavenly Peace) in Beijing was the southern gate of the Imperial City in Beijing.    39 54 26.4 N    116 23 27.9 E
@@ -636,27 +942,25 @@ Zwehrenturm       The Zwehrenturm from 1330 is a remain of Kassel's medieval defensive
 宇治市      Uji is a city in Kyoto prefecture. The Byodoin (an ancient Buddhist temple) and the Ujigami Shrine are famous landmarks in Uji. 34 53 03.7 N    135 47 59.3 E
 小田原市   Odawara, Kanagawa       35 15 52.6 N    139 09 08 E
 屋久島      Yakushima island        30 20 00 N      130 30 00 E
-川崎市      Kawasaki, Kanagawa      35 31 51.2 N    139 42 10.8 E
 广东 Guangdong (广东) is a coastal province in southern China adjacent to Hongkong and Macao.      23 24 00 N      113 30 00 E
-广州 Guangzhou is the capital of Guangdong Province in southern China. The city was formerly known internationally as Canton City or simply Canton.  23 07 43.67 N   113 15 32.31 E
+广州 Guangzhou is the capital of Guangdong Province in southern China. The city was formerly known internationally as Canton City or simply Canton.  23 07 43.66 N   113 15 32.31 E
 広島市      Hiroshima is one of largest cities in Japan and the capital of Hiroshima prefecture.    34 23 06.9 N    132 27 19.1 E
 徳島市      Tokushima is the capital city of Tokushima prefecture on the island of Kyushu in Japan. 34 04 13 N      134 33 17.8 E
-成都 Chengdu is the capital of Sichuan province in China.    30 39 49 N      104 04 00 E
+成都 Chengdu is the capital city and prefecture-level division of Sichuan Province, in southwestern China.   30 39 49 N      104 04 00 E
 景山公园   Jingshan Park, Dongcheng District, Beijing.     39 55 24.5 N    116 23 26.2 E
-杭州 Hangzhou (杭州) is a picturesque city in south China. It is the capital of Zhejiang Province and visited by Marco Polo.       30 15 00 N      120 10 00 E
+杭州 Hangzhou (杭州) is a picturesque city in south China. It is the capital of Zhejiang Province and was visited by Marco Polo.   30 15 00 N      120 10 00 E
 武汉 Wuhan is the capital of the Chinese province of Hubei   30 34 21 N      114 16 45 E
»\95ç\8e\8bé\98\81              28 41 02.76 N   115 52 32.88 E
-熊本市      Kumamoto is the capital city of Kumamoto Prefecture, on the island of Kyushu, Japan.    32 48 10.8 N    130 42 28.3 E
·±å\9c³ Shenzhen is a major city in Guangdong Province, China.  22 32 06 N      114 03 14.4 E
+滕王阁      The Pavilion of Prince Teng or Tengwang Pavilion is a building in the north west of the city of Nanchang, in Jiangxi province, China.   28 41 02.76 N   115 52 32.88 E
 相模原市   Sagamihara, Kanagawa    35 34 17.1 N    139 22 23.3 E
 祇園 Gion (祇園) is a district of Kyoto, Japan, originally developed in the middle ages.   35 00 13 N      135 46 30 E
 福岡市      Fukuoka, Fukuoka        33 35 24.5 N    130 24 06.2 E
 紫禁城      The Forbidden City (紫禁城), located at the centre of Beijing, China, was the imperial palace of the last two imperial dynasties of China (from 1420 to 1924).       39 54 50.1 N    116 23 27.6 E
 西安 Xi'an is an ancient city located in north central China. It was the capital of various dynasties from 1046 B.C. to 907 A.D, and it has been known under a number of different names including most notably Chang'an during the Tang dynasty.    34 16 00 N      108 57 00 E
 逗子市      Zushi (逗子市 Zushi-shi) is a city located in Kanagawa, Japan.       35 17 44.2 N    139 34 49.2 E
\82£è¦\87å¸\82              26 12 44.4 N    127 40 44.8 E
\87\8dåº\86 Chongqing is located in the southwest of China, is China's largest and most populous municipality.      29 33 00 N      106 33 00 E
 鎌倉 Zeniarai Benten shrine  35 19 09.3 N    139 32 48.1 E
-颐和园      The Summer Palace (pinyin: Yiheyuan, 颐和园) is a former imperial palace in northwest of Beijing, China. It has been transformed in a public garden. 39 59 51 N      116 16 08.04 E
-香港 Hong Kong       22 16 01 N      114 11 17 E
-高松市      日本語: 高松市は日本の瀬戸内海沿岸に位置する香川県の県都である。四国の北端に位置することや本州に近いことから、港町として発展してきた歴史をもつ。 34 20 34.1 N    134 02 47.8 E
+颐和园      The Summer Palace is a former imperial palace in northwest of Beijing, China. It has been transformed in a public garden.       39 59 51 N      116 16 08.04 E
+香港         22 16 01 N      114 11 17 E
 高雄市      Kaohsiung is a city in Taiwan.  22 38 00 N      120 16 00 E
-
+서울특별시        Seoul is the capital of South Korea.    37 35 00 N      127 00 00 E
similarity index 99%
rename from tim/prune/function/AddMapSourceDialog.java
rename to tim/prune/function/settings/AddMapSourceDialog.java
index 9207ac5655a82b2464a18daf2d8119cb001225f8..b9ea26f852f10100c4b867cf5d2b63c2df7dda0b 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
similarity index 96%
rename from tim/prune/function/MapSourceListModel.java
rename to tim/prune/function/settings/MapSourceListModel.java
index 489f95d06610c52b3930e64fdc3b954c39b75595..dc7954aa9b4b47dd6169583b2103d936d7ba4fdb 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import javax.swing.AbstractListModel;
 
similarity index 99%
rename from tim/prune/function/SaveConfig.java
rename to tim/prune/function/settings/SaveConfig.java
index c229437f7ac4cccbcb03e852bf9aa39667d48736..d4490512d8599bd80688081208bc73c8f0cc0796 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
similarity index 95%
rename from tim/prune/function/SetAltitudeTolerance.java
rename to tim/prune/function/settings/SetAltitudeTolerance.java
index c51a56a4a43d36c18d08b10aae6cbe4cbe62859a..1049851bedecb2ee4fa813ad145d48597ac34760 100644 (file)
@@ -1,10 +1,11 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import tim.prune.App;
 import tim.prune.DataSubscriber;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
 import tim.prune.data.Unit;
+import tim.prune.function.SingleNumericParameterFunction;
 
 /**
  * Function to set the tolerance for the altitude range calculations
similarity index 99%
rename from tim/prune/function/SetColours.java
rename to tim/prune/function/settings/SetColours.java
index 33b4357dbdd648880e0401d510eded55785cbff3..7c0fdf0ff7a38544d25ae8daa9f20dbe1e94195f 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
diff --git a/tim/prune/function/settings/SetDisplaySettings.java b/tim/prune/function/settings/SetDisplaySettings.java
new file mode 100644 (file)
index 0000000..63286c5
--- /dev/null
@@ -0,0 +1,274 @@
+package tim.prune.function.settings;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WholeNumberField;
+import tim.prune.gui.map.WpIconLibrary;
+
+/**
+ * Class to show the dialog for setting the display settings
+ * like line width, antialiasing, waypoint icons
+ */
+public class SetDisplaySettings extends GenericFunction
+{
+       /**
+        * Inner class to render waypoint icons
+        */
+       class IconComboRenderer extends JLabel implements ListCellRenderer<Integer>
+       {
+               /** Cached icons for each waypoint type */
+               private ImageIcon[] _icons = new ImageIcon[WpIconLibrary.WAYPT_NUMBER_OF_ICONS];
+
+               /** Constructor */
+               IconComboRenderer()
+               {
+                       setOpaque(true);
+               }
+
+               /** Get the label text at the given index */
+               private String getLabel(int inIndex)
+               {
+                       return I18nManager.getText("dialog.displaysettings.wpicon." + WpIconLibrary.getIconName(inIndex));
+               }
+
+               /** Get the image icon at the given index */
+               private ImageIcon getIcon(int inIndex)
+               {
+                       if (_icons[inIndex] == null)
+                       {
+                               _icons[inIndex] = WpIconLibrary.getIconDefinition(inIndex, 1).getImageIcon();
+                       }
+                       return _icons[inIndex];
+               }
+
+               /** @return a label to display the combo box entry */
+               public Component getListCellRendererComponent(
+                       JList<? extends Integer> inList, Integer inValue, int inIndex,
+                       boolean inSelected, boolean inFocus)
+               {
+                       if (inSelected) {
+                               setBackground(inList.getSelectionBackground());
+                               setForeground(inList.getSelectionForeground());
+                       } else {
+                               setBackground(inList.getBackground());
+                               setForeground(inList.getForeground());
+                       }
+                       setIcon(getIcon(inValue));
+                       setText(getLabel(inValue));
+                       return this;
+               }
+       }
+
+
+       // Members of SetDisplaySettings
+       private JDialog _dialog = null;
+       private WholeNumberField _lineWidthField = null;
+       private JCheckBox _antialiasCheckbox = null;
+       private JComboBox<Integer> _wpIconCombobox = null;
+       private JRadioButton[] _sizeRadioButtons = null;
+       private JButton _okButton = null;
+
+
+       /**
+        * Constructor
+        * @param inApp app object
+        */
+       public SetDisplaySettings(App inApp)
+       {
+               super(inApp);
+       }
+
+       /**
+        * Return the name key for this function
+        */
+       public String getNameKey()
+       {
+               return "function.setdisplaysettings";
+       }
+
+       /**
+        * @return the contents of the window as a Component
+        */
+       private Component makeContents()
+       {
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BorderLayout(0, 5));
+               JPanel midPanel = new JPanel();
+               midPanel.setLayout(new BoxLayout(midPanel, BoxLayout.Y_AXIS));
+               midPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
+
+               JPanel linesPanel = new JPanel();
+               linesPanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
+               );
+               GuiGridLayout grid = new GuiGridLayout(linesPanel);
+
+               // line width
+               JLabel lineWidthLabel = new JLabel(I18nManager.getText("dialog.displaysettings.linewidth"));
+               grid.add(lineWidthLabel);
+               _lineWidthField = new WholeNumberField(1);
+               grid.add(_lineWidthField);
+               // Antialiasing
+               _antialiasCheckbox = new JCheckBox(I18nManager.getText("dialog.displaysettings.antialias"), false);
+               grid.add(_antialiasCheckbox);
+               grid.add(new JLabel(""));
+
+               linesPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+               midPanel.add(linesPanel);
+               midPanel.add(Box.createVerticalStrut(10));
+
+               // Panel for waypoint icons
+               JPanel waypointsPanel = new JPanel();
+               waypointsPanel.setBorder(BorderFactory.createCompoundBorder(
+                       BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3))
+               );
+               waypointsPanel.setLayout(new BoxLayout(waypointsPanel, BoxLayout.Y_AXIS));
+               // Select which waypoint icon to use
+               JPanel iconPanel = new JPanel();
+               GuiGridLayout iconGrid = new GuiGridLayout(iconPanel);
+               JLabel headerLabel = new JLabel(I18nManager.getText("dialog.displaysettings.waypointicons"));
+               headerLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+               iconGrid.add(headerLabel);
+               _wpIconCombobox = new JComboBox<Integer>(new Integer[] {0, 1, 2, 3, 4});
+               _wpIconCombobox.setRenderer(new IconComboRenderer());
+               iconGrid.add(_wpIconCombobox);
+               waypointsPanel.add(iconPanel);
+               // Select size of waypoints
+               JPanel sizePanel = new JPanel();
+               sizePanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+               _sizeRadioButtons = new JRadioButton[3];
+               ButtonGroup sizeRadioGroup = new ButtonGroup();
+               final String[] sizeKeys = {"small", "medium", "large"};
+               for (int i=0; i<3; i++)
+               {
+                       _sizeRadioButtons[i] = new JRadioButton(I18nManager.getText("dialog.displaysettings.size." + sizeKeys[i]));
+                       sizeRadioGroup.add(_sizeRadioButtons[i]);
+                       sizePanel.add(_sizeRadioButtons[i]);
+               }
+               waypointsPanel.add(sizePanel);
+               waypointsPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+               midPanel.add(waypointsPanel);
+
+               mainPanel.add(midPanel, BorderLayout.CENTER);
+
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               // OK button
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               finish();
+                       }
+               });
+               buttonPanel.add(_okButton);
+               // Cancel button
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+               return mainPanel;
+       }
+
+
+       /**
+        * Show window
+        */
+       public void begin()
+       {
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()));
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.getContentPane().add(makeContents());
+                       _dialog.pack();
+               }
+               // Set values from config
+               int lineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
+               if (lineWidth < 1 || lineWidth > 4) {lineWidth = 2;}
+               _lineWidthField.setValue(lineWidth);
+               _antialiasCheckbox.setSelected(Config.getConfigBoolean(Config.KEY_ANTIALIAS));
+               _wpIconCombobox.setSelectedIndex(Config.getConfigInt(Config.KEY_WAYPOINT_ICONS));
+               selectIconSizeRadio(Config.getConfigInt(Config.KEY_WAYPOINT_ICON_SIZE));
+               _dialog.setVisible(true);
+       }
+
+       /**
+        * Select the corresponding radio button according to the numeric value
+        * @param inValue numeric value saved in Config
+        */
+       private void selectIconSizeRadio(int inValue)
+       {
+               if (inValue < 0 || inValue >= _sizeRadioButtons.length)
+               {
+                       inValue = 1;
+               }
+               if (_sizeRadioButtons[inValue] != null)
+               {
+                       _sizeRadioButtons[inValue].setSelected(true);
+               }
+       }
+
+       /**
+        * @return numeric value of selected icon size according to radio buttons
+        */
+       private int getSelectedIconSize()
+       {
+               for (int i=0; i<_sizeRadioButtons.length; i++)
+               {
+                       if (_sizeRadioButtons[i] != null && _sizeRadioButtons[i].isSelected())
+                       {
+                               return i;
+                       }
+               }
+               return 1; // default is medium
+       }
+
+       /**
+        * Save settings and close
+        */
+       public void finish()
+       {
+               // update config
+               int lineWidth = _lineWidthField.getValue();
+               if (lineWidth < 1 || lineWidth > 4) {lineWidth = 2;}
+               Config.setConfigInt(Config.KEY_LINE_WIDTH, lineWidth);
+               Config.setConfigBoolean(Config.KEY_ANTIALIAS, _antialiasCheckbox.isSelected());
+               Config.setConfigInt(Config.KEY_WAYPOINT_ICONS, _wpIconCombobox.getSelectedIndex());
+               Config.setConfigInt(Config.KEY_WAYPOINT_ICON_SIZE, getSelectedIconSize());
+               // refresh display
+               UpdateMessageBroker.informSubscribers(DataSubscriber.MAPSERVER_CHANGED);
+               _dialog.dispose();
+       }
+}
similarity index 97%
rename from tim/prune/function/SetLanguage.java
rename to tim/prune/function/settings/SetLanguage.java
index 832b5211b67ed0615535754592ee16f7cc17f728..642e71553834f97064416163ebad2b7ac37a1905 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
@@ -43,13 +43,13 @@ public class SetLanguage extends GenericFunction
        /** Names of languages for display in dropdown (not translated) */
        private static final String[] LANGUAGE_NAMES = {"afrikaans", "\u010de\u0161tina", "deutsch", "english", "american english",
                "espa\u00F1ol", "fran\u00E7ais", "italiano", "magyar", "nederlands", "polski", "portugu\u00EAs", "rom\u00E2n\u0103",
-               "\u0440\u0443\u0441\u0441\u043a\u0438\u0439 (russian)", "\u4e2d\u6587 (chinese)",
+               "suomi", "\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", "\u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430 \u043c\u043e\u0432\u0430 (ukrainian)"
+               "\u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430 \u043c\u043e\u0432\u0430 (ukrainian)"
        };
        /** Associated language codes (must be in same order as names!) */
        private static final String[] LANGUAGE_CODES = {"af", "cz", "de", "en", "en_us", "es", "fr", "it", "hu",
-               "nl", "pl", "pt", "ro", "ru", "zh", "ja", "ko", "de_ch", "tr", "uk"
+               "nl", "pl", "pt", "ro", "fi", "ru", "zh", "ja", "ko", "de_ch", "uk"
        };
 
 
similarity index 99%
rename from tim/prune/function/SetMapBgFunction.java
rename to tim/prune/function/settings/SetMapBgFunction.java
index a875d009cf06224603c38a5c860a4a0bea16e486..862c9d8bf797ddc6c7643b93a30f8a5aa639f8aa 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.Component;
similarity index 99%
rename from tim/prune/function/SetPathsFunction.java
rename to tim/prune/function/settings/SetPathsFunction.java
index a5131af7f8aa960b56c3bcc108f795c857cbc0ac..a5d08711e61841fe21f8763b899870c2d7bd0ba0 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function;
+package tim.prune.function.settings;
 
 import java.awt.BorderLayout;
 import java.awt.FlowLayout;
index 91a7d526d5fd4b0655fa5b9f8144ad12e5f60256..6fec0754155f7cb667466234b19434229506633a 100644 (file)
@@ -1,77 +1,20 @@
 package tim.prune.function.sew;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
 import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
 
 import tim.prune.App;
-import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.data.DataPoint;
-import tim.prune.data.Distance;
-import tim.prune.data.Field;
-import tim.prune.data.Unit;
-import tim.prune.data.UnitSetLibrary;
-import tim.prune.gui.WholeNumberField;
+import tim.prune.function.DistanceTimeLimitFunction;
 import tim.prune.undo.UndoSplitSegments;
 
 /**
  * Function to split a track into segments using
  * either a distance limit or a time limit
  */
-public class SplitSegmentsFunction extends GenericFunction
+public class SplitSegmentsFunction extends DistanceTimeLimitFunction
 {
-       /** Dialog */
-       private JDialog _dialog = null;
-       /** Radio buttons for splitting by distance and time */
-       private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
-       /** Dropdown for selecting distance units */
-       private JComboBox<String> _distUnitsDropdown = null;
-       /** Text field for entering distance */
-       private WholeNumberField _distanceField = null;
-       /** Text fields for entering distance */
-       private WholeNumberField _limitHourField = null, _limitMinField = null;
-       /** Ok button */
-       private JButton _okButton = null;
-
-
-       /**
-        * React to item changes and key presses
-        */
-       private abstract class ChangeListener extends KeyAdapter implements ItemListener
-       {
-               /** Method to be implemented */
-               public abstract void optionsChanged();
-
-               /** Item changed in ItemListener */
-               public void itemStateChanged(ItemEvent arg0) {
-                       optionsChanged();
-               }
-
-               /** Key released in KeyListener */
-               public void keyReleased(KeyEvent arg0) {
-                       optionsChanged();
-               }
-       }
-
        /**
         * Constructor
         */
@@ -86,161 +29,18 @@ public class SplitSegmentsFunction extends GenericFunction
                return "function.splitsegments";
        }
 
-       /**
-        * Begin the function
-        */
-       public void begin()
-       {
-               if (_dialog == null)
-               {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-                       _dialog.getContentPane().add(makeDialogComponents());
-                       _dialog.pack();
-               }
-               enableOkButton();
-               // TODO: Maybe set distance units according to current Config setting?
-               final boolean hasTimestamps = _app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP);
-               _timeLimitRadio.setEnabled(hasTimestamps);
-               _dialog.setVisible(true);
-       }
-
-       /**
-        * Create dialog components
-        * @return Panel containing all gui elements in dialog
-        */
-       private Component makeDialogComponents()
-       {
-               JPanel dialogPanel = new JPanel();
-               dialogPanel.setLayout(new BorderLayout(5, 5));
-
-               // Make radio buttons for three different options
-               _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
-               _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
-               ButtonGroup radioGroup = new ButtonGroup();
-               radioGroup.add(_distLimitRadio);
-               radioGroup.add(_timeLimitRadio);
-
-               // central panel for limits
-               JPanel limitsPanel = new JPanel();
-               limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
-               limitsPanel.add(Box.createVerticalStrut(8));
-               ChangeListener optionsChangedListener = new ChangeListener() {
-                       public void optionsChanged() {
-                               enableOkButton();
-                       }
-               };
-               // distance limits
-               JPanel distLimitPanel = new JPanel();
-               distLimitPanel.setLayout(new FlowLayout());
-               _distLimitRadio.setSelected(true);
-               _distLimitRadio.addItemListener(optionsChangedListener);
-               distLimitPanel.add(_distLimitRadio);
-               _distanceField = new WholeNumberField(3);
-               _distanceField.addKeyListener(optionsChangedListener);
-               distLimitPanel.add(_distanceField);
-               String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
-                       I18nManager.getText("units.miles")};
-               _distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
-               _distUnitsDropdown.addItemListener(optionsChangedListener);
-               distLimitPanel.add(_distUnitsDropdown);
-               distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               limitsPanel.add(distLimitPanel);
-
-               // time limit panel
-               JPanel timeLimitPanel = new JPanel();
-               timeLimitPanel.setLayout(new FlowLayout());
-               _timeLimitRadio.addItemListener(optionsChangedListener);
-               timeLimitPanel.add(_timeLimitRadio);
-               _limitHourField = new WholeNumberField(2);
-               _limitHourField.addKeyListener(optionsChangedListener);
-               timeLimitPanel.add(_limitHourField);
-               timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
-               _limitMinField = new WholeNumberField(3);
-               _limitMinField.addKeyListener(optionsChangedListener);
-               timeLimitPanel.add(_limitMinField);
-               timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
-               timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               limitsPanel.add(timeLimitPanel);
-
-               dialogPanel.add(limitsPanel, BorderLayout.NORTH);
-
-               // button panel at bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               // OK button
-               _okButton = new JButton(I18nManager.getText("button.ok"));
-               _okButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               performSplit();
-                       }
-               });
-               buttonPanel.add(_okButton);
-               // Cancel button
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               _dialog.dispose();
-                       }
-               });
-               cancelButton.addKeyListener(new KeyAdapter() {
-                       public void keyPressed(KeyEvent inE) {
-                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
-                       }
-               });
-               buttonPanel.add(cancelButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               return dialogPanel;
-       }
-
-       /**
-        * Enable or disable the OK button according to the inputs
-        */
-       private void enableOkButton()
-       {
-               boolean enabled = false;
-               if (_distLimitRadio.isSelected()) {
-                       enabled = _distanceField.getValue() > 0;
-               }
-               else if (_timeLimitRadio.isSelected()) {
-                       enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
-               }
-               _okButton.setEnabled(enabled);
-
-               // Also enable/disable the other fields
-               _distanceField.setEnabled(_distLimitRadio.isSelected());
-               _distUnitsDropdown.setEnabled(_distLimitRadio.isSelected());
-               _limitHourField.setEnabled(_timeLimitRadio.isSelected());
-               _limitMinField.setEnabled(_timeLimitRadio.isSelected());
-       }
 
        /**
         * The dialog has been completed and OK pressed, so do the split
         */
-       private void performSplit()
+       protected void performFunction()
        {
                // Split either by distance or time
-               boolean checkTimeLimit = _timeLimitRadio.isSelected()
-                       && (_limitHourField.getValue() > 0 || _limitMinField.getValue() > 0);
-               int timeLimitSeconds = 0;
-               if (checkTimeLimit)
-               {
-                       timeLimitSeconds = _limitHourField.getValue() * 60 * 60
-                               + _limitMinField.getValue() * 60;
-                       if (timeLimitSeconds <= 0) {checkTimeLimit = false;}
-               }
-               double distLimitRadians = 0.0;
-               final boolean checkDistLimit = _distLimitRadio.isSelected()
-                       && _distanceField.getValue() > 0;
-               if (checkDistLimit)
-               {
-                       final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES,
-                               UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
-                       Unit distUnit = distUnits[_distUnitsDropdown.getSelectedIndex()];
-                       distLimitRadians = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
-               }
-               if (!checkTimeLimit && !checkDistLimit) {
+               final int timeLimitSeconds = getTimeLimitInSeconds();
+               final boolean splitByTime = (timeLimitSeconds > 0);
+               final double distLimitRadians = getDistanceLimitRadians();
+               final boolean splitByDistance = (distLimitRadians > 0.0);
+               if (!splitByTime && !splitByDistance) {
                        return; // neither option selected
                }
 
@@ -257,8 +57,8 @@ public class SplitSegmentsFunction extends GenericFunction
                        if (!currPoint.isWaypoint())
                        {
                                boolean splitHere = (prevPoint != null)
-                                       && ((checkDistLimit && DataPoint.calculateRadiansBetween(prevPoint, currPoint) > distLimitRadians)
-                                               || (checkTimeLimit && currPoint.hasTimestamp() && prevPoint.hasTimestamp()
+                                       && ((splitByDistance && DataPoint.calculateRadiansBetween(prevPoint, currPoint) > distLimitRadians)
+                                               || (splitByTime && currPoint.hasTimestamp() && prevPoint.hasTimestamp()
                                                        && currPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()) > timeLimitSeconds));
                                if (splitHere && !currPoint.getSegmentStart())
                                {
diff --git a/tim/prune/gui/CombinedListAndModel.java b/tim/prune/gui/CombinedListAndModel.java
new file mode 100644 (file)
index 0000000..4b94ceb
--- /dev/null
@@ -0,0 +1,110 @@
+package tim.prune.gui;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.ListSelectionModel;
+
+import tim.prune.I18nManager;
+
+
+/**
+ * Listbox class which also contains its own string model.
+ * Also has the ability to limit its size and show a single
+ * text instead of a huge list
+ */
+public class CombinedListAndModel extends JList<String>
+{
+       private DefaultListModel<String> _model = null;
+       private final int _key;
+       private int _maxNumEntries = 0;
+       private boolean _tooManyEntries = false;
+       private boolean _unlimited = false;
+
+
+       /**
+        * Constructor
+        */
+       public CombinedListAndModel(int inKey)
+       {
+               setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               _model = new DefaultListModel<String>();
+               setModel(_model);
+               _key = inKey;
+       }
+
+       /**
+        * @param inMaxNum maximum number of entries to allow
+        */
+       public void setMaxNumEntries(int inMaxNum)
+       {
+               _maxNumEntries = inMaxNum;
+       }
+
+       /**
+        * @param inUnlimited true if list is temporarily unlimited
+        */
+       public void setUnlimited(boolean inUnlimited)
+       {
+               _unlimited = inUnlimited;
+       }
+
+       /**
+        * @return key
+        */
+       public int getKey()
+       {
+               return _key;
+       }
+
+       /**
+        * @param inItem String to add to the list
+        */
+       public void addItem(String inItem)
+       {
+               if (!_tooManyEntries)
+               {
+                       _model.addElement(inItem);
+                       if (_maxNumEntries > 0 && !_unlimited
+                               && _model.getSize() > _maxNumEntries)
+                       {
+                               _tooManyEntries = true;
+                               _model.clear();
+                               _model.addElement(I18nManager.getText("dialog.settimezone.list.toomany"));
+                       }
+               }
+       }
+
+       /**
+        * @return the selected String, or null
+        */
+       public String getSelectedItem()
+       {
+               final int selectedIndex = getSelectedIndex();
+               if (_tooManyEntries || selectedIndex < 0)
+               {
+                       return null;
+               }
+               return _model.getElementAt(selectedIndex);
+       }
+
+       /**
+        * Clear the list
+        */
+       public void clear()
+       {
+               _model.clear();
+               _tooManyEntries = false;
+               _unlimited = false;
+       }
+
+       /**
+        * @param inItem item to select
+        */
+       public void selectItem(String inItem)
+       {
+               if (!_tooManyEntries && inItem != null)
+               {
+                       this.setSelectedValue(inItem, true);
+               }
+       }
+}
index fb868e8bf108619f48aefefff6c8dbabea2a688f..2fa98dc9c0907d52e19489372c15eff227f5e37a 100644 (file)
@@ -8,6 +8,7 @@ import java.awt.Font;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.util.TimeZone;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
@@ -25,6 +26,7 @@ import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.Config;
+import tim.prune.config.TimezoneHelper;
 import tim.prune.data.AltitudeRange;
 import tim.prune.data.AudioClip;
 import tim.prune.data.Coordinate;
@@ -86,6 +88,8 @@ public class DetailsDisplay extends GenericDisplay
        // Units
        private JComboBox<String> _coordFormatDropdown = null;
        private JComboBox<String> _distUnitsDropdown = null;
+       // Timezone
+       private TimeZone _timezone = null;
 
        // Cached labels
        private static final String LABEL_POINT_SELECTED = I18nManager.getText("details.index.selected") + ": ";
@@ -299,6 +303,10 @@ public class DetailsDisplay extends GenericDisplay
                UnitSet unitSet = UnitSetLibrary.getUnitSet(_distUnitsDropdown.getSelectedIndex());
                String distUnitsStr = I18nManager.getText(unitSet.getDistanceUnit().getShortnameKey());
                String speedUnitsStr = I18nManager.getText(unitSet.getSpeedUnit().getShortnameKey());
+               if (_timezone == null || (inUpdateType | UNITS_CHANGED) > 0) {
+                       _timezone = TimezoneHelper.getSelectedTimezone();
+               }
+
                if (_track == null || currentPoint == null)
                {
                        _indexLabel.setText(I18nManager.getText("details.nopointselection"));
@@ -326,11 +334,13 @@ public class DetailsDisplay extends GenericDisplay
                                (LABEL_POINT_ALTITUDE + currentPoint.getAltitude().getValue(altUnit) + " " +
                                I18nManager.getText(altUnit.getShortnameKey()))
                                : "");
-                       if (currentPoint.hasTimestamp()) {
-                               _ptDateLabel.setText(LABEL_POINT_DATE + currentPoint.getTimestamp().getDateText());
-                               _ptTimeLabel.setText(LABEL_POINT_TIME + currentPoint.getTimestamp().getTimeText());
+                       if (currentPoint.hasTimestamp())
+                       {
+                               _ptDateLabel.setText(LABEL_POINT_DATE + currentPoint.getTimestamp().getDateText(_timezone));
+                               _ptTimeLabel.setText(LABEL_POINT_TIME + currentPoint.getTimestamp().getTimeText(_timezone));
                        }
-                       else {
+                       else
+                       {
                                _ptDateLabel.setText("");
                                _ptTimeLabel.setText("");
                        }
@@ -477,7 +487,9 @@ public class DetailsDisplay extends GenericDisplay
                        String shortPath = shortenPath(fullPath);
                        _photoPathLabel.setText(fullPath == null ? "" : LABEL_FULL_PATH + shortPath);
                        _photoPathLabel.setToolTipText(currentPhoto.getFullPath());
-                       _photoTimestampLabel.setText(currentPhoto.hasTimestamp()?(LABEL_POINT_TIME + currentPhoto.getTimestamp().getText()):"");
+                       _photoTimestampLabel.setText(currentPhoto.hasTimestamp() ?
+                               (LABEL_POINT_TIME + currentPhoto.getTimestamp().getText(_timezone))
+                               : "");
                        _photoConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
                                + (currentPhoto.getCurrentStatus() == Photo.Status.NOT_CONNECTED ?
                                        I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes")));
@@ -513,7 +525,9 @@ public class DetailsDisplay extends GenericDisplay
                        String shortPath = shortenPath(fullPath);
                        _audioPathLabel.setText(fullPath == null ? "" : LABEL_FULL_PATH + shortPath);
                        _audioPathLabel.setToolTipText(fullPath == null ? "" : fullPath);
-                       _audioTimestampLabel.setText(currentAudio.hasTimestamp()?(LABEL_POINT_TIME + currentAudio.getTimestamp().getText()):"");
+                       _audioTimestampLabel.setText(currentAudio.hasTimestamp() ?
+                               (LABEL_POINT_TIME + currentAudio.getTimestamp().getText(_timezone))
+                               : "");
                        int audioLength = currentAudio.getLengthInSeconds();
                        _audioLengthLabel.setText(audioLength < 0?"":LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(audioLength));
                        _audioConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
@@ -571,7 +585,7 @@ public class DetailsDisplay extends GenericDisplay
                        {
                                result = inCoord.substring(0, chopPos);
                                // Maybe there's an exponential in there too which needs to be appended
-                               int expPos = inCoord.toUpperCase().indexOf("E",  chopPos);
+                               int expPos = inCoord.toUpperCase().indexOf("E", chopPos);
                                if (expPos > 0 && expPos < (inCoord.length()-1))
                                {
                                        result += inCoord.substring(expPos);
index fd0a888d111e4805cf020c8adb8ac8653004e3a3..a4572be8ba2f196b8ebc2d99499dc906ae1b5da4 100644 (file)
@@ -30,6 +30,8 @@ public abstract class IconManager
        public static final String POINTS_DISCONNECTED_BUTTON = "points_disconnected.png";
        /** Icon for points hidden, just lines icon on main map display */
        public static final String POINTS_HIDDEN_BUTTON = "points_hidden.png";
+       /** Icon for points, lines and arrows on main map display */
+       public static final String POINTS_WITH_ARROWS_BUTTON = "points_arrows.png";
         /** Icon for edit mode button on main map display when not selected */
        public static final String EDIT_MODE_BUTTON = "drag_points_icon.gif";
         /** Icon for edit mode button on main map display when selected */
@@ -86,6 +88,9 @@ public abstract class IconManager
        /** Icon for a given entry being empty (blank) */
        public static final String ENTRY_NONE = "entry_none.gif";
 
+       public static final String WAYPOINT_ICON_PREFIX = "wpicon_";
+       public static final String WAYPOINT_ICON_SUFFIX = ".png";
+
        /**
         * Get the specified image
         * @param inFilename filename of image (using constants)
index d152ecca5b1c78549d6fcb7ea0f65878984a5436..c3c4c2088d796e71d9ca2bcf9aaec1c6f35b9076 100644 (file)
@@ -39,6 +39,9 @@ public abstract class ImageUtils
         */
        public static BufferedImage createScaledImage(Image inImage, int inWidth, int inHeight)
        {
+               if (inWidth <= 0 || inHeight <= 0) {
+                       return null;
+               }
                // create smaller image and force its loading
                Image smallerImage = inImage.getScaledInstance(inWidth, inHeight, Image.SCALE_SMOOTH);
                Image tempImage = new ImageIcon(smallerImage).getImage();
index 38ed02b04d158abd545a21fc005e0307ba3ef229..08ecf18677ed08ad8c2eb3023f5daec0c6c0b33b 100644 (file)
@@ -51,7 +51,6 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _exportKmlItem = 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;
@@ -82,6 +81,7 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _rearrangeWaypointsItem = null;
        private JMenuItem _splitSegmentsItem = null;
        private JMenuItem _sewSegmentsItem = null;
+       private JMenuItem _createMarkerWaypointsItem = null;
        private JMenuItem _cutAndMoveItem = null;
        private JMenuItem _convertNamesToTimesItem = null;
        private JMenuItem _deleteFieldValuesItem = null;
@@ -94,9 +94,9 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _lookupSrtmItem = null;
        private JMenuItem _downloadSrtmItem = null;
        private JMenuItem _nearbyWikipediaItem = null;
+       private JMenuItem _nearbyOsmPoiItem = null;
        private JMenuItem _showPeakfinderItem = null;
        private JMenuItem _showGeohackItem = null;
-       private JMenuItem _showPanoramioItem = null;
        private JMenuItem _searchOpencachingDeItem = null;
        private JMenuItem _searchMapillaryItem = null;
        private JMenuItem _downloadOsmItem = null;
@@ -123,7 +123,6 @@ public class MenuManager implements DataSubscriber
        private JMenuItem _correlateAudiosItem = null;
        private JMenuItem _selectNoAudioItem = null;
        private JCheckBoxMenuItem _onlineCheckbox = null;
-       private JCheckBoxMenuItem _antialiasCheckbox = null;
        private JCheckBoxMenuItem _autosaveSettingsCheckbox = null;
 
        // ActionListeners for reuse by menu and toolbar
@@ -236,9 +235,6 @@ public class MenuManager implements DataSubscriber
                // Pov
                _exportPovItem = makeMenuItem(FunctionLibrary.FUNCTION_POVEXPORT, false);
                fileMenu.add(_exportPovItem);
-               // Svg
-               _exportSvgItem = makeMenuItem(FunctionLibrary.FUNCTION_SVGEXPORT, false);
-               fileMenu.add(_exportSvgItem);
                // Image
                _exportImageItem = makeMenuItem(FunctionLibrary.FUNCTION_IMAGEEXPORT, false);
                fileMenu.add(_exportImageItem);
@@ -289,12 +285,12 @@ public class MenuManager implements DataSubscriber
                onlineMenu.add(_nearbyWikipediaItem);
                JMenuItem searchWikipediaNamesItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_WIKIPEDIA);
                onlineMenu.add(searchWikipediaNamesItem);
+               _nearbyOsmPoiItem = makeMenuItem(FunctionLibrary.FUNCTION_SEARCH_OSMPOIS);
+               onlineMenu.add(_nearbyOsmPoiItem);
                _showPeakfinderItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_PEAKFINDER, "webservice.peakfinder"), false);
                onlineMenu.add(_showPeakfinderItem);
                _showGeohackItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_GEOHACK, "webservice.geohack"), false);
                onlineMenu.add(_showGeohackItem);
-               _showPanoramioItem = makeMenuItem(new WebMapFunction(_app, UrlGenerator.WebService.MAP_SOURCE_PANORAMIO, "webservice.panoramio"), false);
-               onlineMenu.add(_showPanoramioItem);
 
                onlineMenu.addSeparator();
                _searchOpencachingDeItem = makeMenuItem(new SearchOpenCachingDeFunction(_app), false);
@@ -358,6 +354,9 @@ public class MenuManager implements DataSubscriber
                // Sew track segments
                _sewSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SEW_SEGMENTS, false);
                trackMenu.add(_sewSegmentsItem);
+               // Create marker waypoints
+               _createMarkerWaypointsItem = makeMenuItem(FunctionLibrary.FUNCTION_CREATE_MARKER_WAYPOINTS, false);
+               trackMenu.add(_createMarkerWaypointsItem);
                trackMenu.addSeparator();
                _learnEstimationParams = makeMenuItem(FunctionLibrary.FUNCTION_LEARN_ESTIMATION_PARAMS, false);
                trackMenu.add(_learnEstimationParams);
@@ -637,22 +636,15 @@ public class MenuManager implements DataSubscriber
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_PATHS));
                // Set colours
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_COLOURS));
-               // Set line width used for drawing
-               settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_LINE_WIDTH)));
-               // Use antialias or not
-               _antialiasCheckbox = new JCheckBoxMenuItem(I18nManager.getText("menu.settings.antialias"), false);
-               _antialiasCheckbox.setSelected(Config.getConfigBoolean(Config.KEY_ANTIALIAS));
-               _antialiasCheckbox.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               Config.setConfigBoolean(Config.KEY_ANTIALIAS, _antialiasCheckbox.isSelected());
-                               UpdateMessageBroker.informSubscribers(MAPSERVER_CHANGED);
-                       }
-               });
-                       settingsMenu.add(_antialiasCheckbox);
+               // display settings
+               JMenuItem setDisplaySettingsItem = makeMenuItem(FunctionLibrary.FUNCTION_SET_DISPLAY_SETTINGS);
+               settingsMenu.add(setDisplaySettingsItem);
                // Set language
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LANGUAGE));
                // Set altitude tolerance
                settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_ALTITUDE_TOLERANCE)));
+               // Set timezone
+               settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_TIMEZONE));
                settingsMenu.addSeparator();
                // Save configuration
                settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SAVECONFIG));
@@ -865,15 +857,16 @@ public class MenuManager implements DataSubscriber
                _exportKmlItem.setEnabled(hasData);
                _exportGpxItem.setEnabled(hasData);
                _exportPovItem.setEnabled(hasMultiplePoints);
-               _exportSvgItem.setEnabled(hasMultiplePoints);
                _exportImageItem.setEnabled(hasMultiplePoints);
                _compressItem.setEnabled(hasData);
                _markRectangleItem.setEnabled(hasData);
                _markUphillLiftsItem.setEnabled(hasData && _track.hasAltitudeData());
                _deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
                _rearrangeWaypointsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
-               _splitSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
-               _sewSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
+               final boolean hasSeveralTrackPoints = hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3;
+               _splitSegmentsItem.setEnabled(hasSeveralTrackPoints);
+               _sewSegmentsItem.setEnabled(hasSeveralTrackPoints);
+               _createMarkerWaypointsItem.setEnabled(hasSeveralTrackPoints);
                _selectAllItem.setEnabled(hasData);
                _selectNoneItem.setEnabled(hasData);
                _show3dItem.setEnabled(hasMultiplePoints);
@@ -885,6 +878,7 @@ public class MenuManager implements DataSubscriber
                _uploadGpsiesItem.setEnabled(hasData && _track.hasTrackPoints());
                _lookupSrtmItem.setEnabled(hasData);
                _nearbyWikipediaItem.setEnabled(hasData);
+               _nearbyOsmPoiItem.setEnabled(hasData);
                _downloadOsmItem.setEnabled(hasData);
                _getWeatherItem.setEnabled(hasData);
                _findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
@@ -913,7 +907,6 @@ public class MenuManager implements DataSubscriber
                _duplicatePointItem.setEnabled(hasPoint);
                _showPeakfinderItem.setEnabled(hasPoint);
                _showGeohackItem.setEnabled(hasPoint);
-               _showPanoramioItem.setEnabled(hasPoint);
                _searchOpencachingDeItem.setEnabled(hasPoint);
                _searchMapillaryItem.setEnabled(hasPoint);
                // is it a waypoint?
similarity index 62%
rename from tim/prune/gui/TripleStateCheckBox.java
rename to tim/prune/gui/MultiStateCheckBox.java
index 6bd60019340244d8db20c77886df93dc4882928e..65a5e877becd650795be3f89f78e7454febb8cf9 100644 (file)
@@ -7,16 +7,19 @@ import javax.swing.ImageIcon;
 import javax.swing.JCheckBox;
 
 /**
- * Class to represent a checkbox with three states, through which it cycles
- * Instead of calling isChecked, need to use getCurrentState which will
- * return 0, 1 or 2
+ * Class to represent a checkbox with multiple states, through which it cycles.
+ * Instead of calling isChecked, callers need to use getCurrentState which will
+ * return 0 up to (n-1) for n states.
  */
-public class TripleStateCheckBox extends JCheckBox implements ItemListener
+public class MultiStateCheckBox extends JCheckBox implements ItemListener
 {
        /** Array of icons to be used */
-       private ImageIcon[] _icons = new ImageIcon[3];
-       /** Current state 0, 1 or 2 */
+       private ImageIcon[] _icons = null;
+       /** Current state 0 to n-1 */
        private int _currState = 0;
+       /** Number of states n */
+       private final int _numStates;
+
 
        /** Inner class to proxy the listening events */
        private class ProxyListener implements ItemListener
@@ -33,22 +36,31 @@ public class TripleStateCheckBox extends JCheckBox implements ItemListener
                }
        }
 
-       /** Constructor */
-       public TripleStateCheckBox()
+       /**
+        * Constructor
+        * @param inNumStates number of states to cycle through
+        */
+       public MultiStateCheckBox(int inNumStates)
        {
+               _numStates = (inNumStates > 0) ? inNumStates : 1;
+               _icons = new ImageIcon[_numStates];
                addItemListener(this);
        }
 
-       /** Set the current state */
+       /**
+        * @param inState state to set
+        */
        public void setCurrentState(int inState)
        {
-               _currState = inState % 3;
+               _currState = inState % _numStates;
                setIcon(_icons[_currState]);
                setSelected(false);
-               setSelectedIcon(_icons[(_currState+1)%3]);
+               setSelectedIcon(_icons[(_currState+1) % _numStates]);
        }
 
-       /** @return current state 0, 1 or 2 */
+       /**
+        * @return current state 0 to n-1
+        */
        public int getCurrentState()
        {
                return _currState;
@@ -56,12 +68,12 @@ public class TripleStateCheckBox extends JCheckBox implements ItemListener
 
        /**
         * Set the icon to use for the given index
-        * @param inIndex index 0, 1 or 2
+        * @param inIndex index 0 to n-1
         * @param inIcon icon to use for that state
         */
        public void setIcon(int inIndex, ImageIcon inIcon)
        {
-               _icons[inIndex % 3] = inIcon;
+               _icons[inIndex % _numStates] = inIcon;
        }
 
        @Override
index 3e8b093a644c5c57ddfa2a078210b964be346e29..458ee8d84d866945d92d4cf51fb2b1da83ee5cd4 100644 (file)
@@ -129,14 +129,16 @@ public class PhotoThumbnail extends JPanel implements Runnable
        {
                if (_inPanel)
                {
-                       // use either exif thumbnail or photo scaled down to sensible size
+                       _thumbnail = null;
+                       // try to use exif thumbnail
                        if (_photo.getExifThumbnail() != null) {
                                // Use exif thumbnail
                                Image image = new ImageIcon(_photo.getExifThumbnail()).getImage();
                                _thumbnail = ImageUtils.createScaledImage(image, image.getWidth(null), image.getHeight(null));
                                image = null;
                        }
-                       else
+                       // Maybe there's no thumbnail, maybe the load of the thumbnail failed
+                       if (_thumbnail == null)
                        {
                                // no exif thumbnail available, going to have to read whole thing
                                int picWidth = _photo.getWidth();
index facbb26a7b9ac9cb5ad2023dcfd61439176646a5..84f1553c4cf7e0e1e6abadef2a9d232b4264d28b 100644 (file)
@@ -46,7 +46,7 @@ public class ColourerCaretaker implements DataSubscriber
        public void dataUpdated(byte inUpdateType)
        {
                if ((inUpdateType &
-                       (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED)) > 0
+                       (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED | DataSubscriber.UNITS_CHANGED)) > 0
                        && _colourer != null)
                {
                        _colourer.calculateColours(_app.getTrackInfo());
index 5a6306b5fcfd8e71c96e0525d1bd84a463234fe7..6dfeca63d424a81df43e973be75c112db2f0932a 100644 (file)
@@ -6,20 +6,23 @@ import java.util.Calendar;
 import java.util.HashMap;
 import java.util.TimeZone;
 
+import tim.prune.config.Config;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Timestamp;
 import tim.prune.data.Track;
 import tim.prune.data.TrackInfo;
 
 /**
- * Point colourer giving a different colour to each date
- * Uses the system timezone so may give funny results for
- * data from other timezones (eg far-away holidays)
+ * Point colourer giving a different colour to each date.
+ * Uses the currently selected timezone, so the results
+ * may be different when selecting a different timezone
  */
 public class DateColourer extends DiscretePointColourer
 {
        // Doesn't really matter what format is used here, as long as dates are different
        private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
+       // Selected timezone for deciding which date a timestamp falls on
+       private TimeZone _selectedTimezone = null;
 
        /**
         * Constructor
@@ -37,8 +40,21 @@ public class DateColourer extends DiscretePointColourer
         * @param inTrackInfo track info object
         */
        @Override
-       public void calculateColours(TrackInfo inTrackInfo)
+       public synchronized void calculateColours(TrackInfo inTrackInfo)
        {
+               // Note, this method needs to be synchronized because otherwise the
+               // Calendar objects in the different threads get confused and the
+               // wrong colours are generated.
+
+               // TODO: Move this timezone-selection into a helper for use by others?
+               // Select the current timezone
+               final String zoneId = Config.getConfigString(Config.KEY_TIMEZONE_ID);
+               if (zoneId == null || zoneId.equals("")) {
+                       _selectedTimezone = TimeZone.getDefault();
+               }
+               else {
+                       _selectedTimezone = TimeZone.getTimeZone(zoneId);
+               }
                // initialise the array to the right size
                Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
                final int numPoints = track == null ? 0 : track.getNumPoints();
@@ -88,24 +104,25 @@ public class DateColourer extends DiscretePointColourer
                                setColour(i, dayIndex);
                        }
                }
+
                // generate the colours needed
                generateDiscreteColours(usedDates.size() + 1);
        }
 
 
        /**
-        * Find which date (in the system timezone) the given timestamp falls on
+        * Find which date (in the currently selected timezone) the given timestamp falls on
         * @param inTimestamp timestamp
         * @return String containing description of date, or null
         */
-       private static String getDate(Timestamp inTimestamp)
+       private String getDate(Timestamp inTimestamp)
        {
                if (inTimestamp == null || !inTimestamp.isValid()) {
                        return null;
                }
-               Calendar cal = inTimestamp.getCalendar();
-               // use system time zone
-               cal.setTimeZone(TimeZone.getDefault());
+               Calendar cal = inTimestamp.getCalendar(null);
+               // use selected time zone, not system one
+               DEFAULT_DATE_FORMAT.setTimeZone(_selectedTimezone);
                return DEFAULT_DATE_FORMAT.format(cal.getTime());
        }
 }
diff --git a/tim/prune/gui/images/points_arrows.png b/tim/prune/gui/images/points_arrows.png
new file mode 100644 (file)
index 0000000..4957016
Binary files /dev/null and b/tim/prune/gui/images/points_arrows.png differ
index 1891af0b696ca136b4c973e562c79a878e4ea460..b44000c2f8e850ebaa6e156e7f4b71bb5516686e 100644 (file)
Binary files a/tim/prune/gui/images/points_connected.png and b/tim/prune/gui/images/points_connected.png differ
index 37f2c0fe8a409a84955468519e20342517a336b5..98d7ac37edb13e66b93f77a161456b97f4f4455f 100644 (file)
Binary files a/tim/prune/gui/images/points_disconnected.png and b/tim/prune/gui/images/points_disconnected.png differ
index 452015cf3c03a19be5cc5c4eecc220e3893c6356..b67a884ca87e5cb0f7f98eca707dae31b57bc17d 100644 (file)
Binary files a/tim/prune/gui/images/points_hidden.png and b/tim/prune/gui/images/points_hidden.png differ
diff --git a/tim/prune/gui/images/wpicon_default_m.png b/tim/prune/gui/images/wpicon_default_m.png
new file mode 100644 (file)
index 0000000..95fffb4
Binary files /dev/null and b/tim/prune/gui/images/wpicon_default_m.png differ
diff --git a/tim/prune/gui/images/wpicon_pin_l.png b/tim/prune/gui/images/wpicon_pin_l.png
new file mode 100644 (file)
index 0000000..66354c1
Binary files /dev/null and b/tim/prune/gui/images/wpicon_pin_l.png differ
diff --git a/tim/prune/gui/images/wpicon_pin_m.png b/tim/prune/gui/images/wpicon_pin_m.png
new file mode 100644 (file)
index 0000000..86c6c3d
Binary files /dev/null and b/tim/prune/gui/images/wpicon_pin_m.png differ
diff --git a/tim/prune/gui/images/wpicon_pin_s.png b/tim/prune/gui/images/wpicon_pin_s.png
new file mode 100644 (file)
index 0000000..47567c9
Binary files /dev/null and b/tim/prune/gui/images/wpicon_pin_s.png differ
diff --git a/tim/prune/gui/images/wpicon_plectrum_l.png b/tim/prune/gui/images/wpicon_plectrum_l.png
new file mode 100644 (file)
index 0000000..a108883
Binary files /dev/null and b/tim/prune/gui/images/wpicon_plectrum_l.png differ
diff --git a/tim/prune/gui/images/wpicon_plectrum_m.png b/tim/prune/gui/images/wpicon_plectrum_m.png
new file mode 100644 (file)
index 0000000..1b79ac2
Binary files /dev/null and b/tim/prune/gui/images/wpicon_plectrum_m.png differ
diff --git a/tim/prune/gui/images/wpicon_plectrum_s.png b/tim/prune/gui/images/wpicon_plectrum_s.png
new file mode 100644 (file)
index 0000000..e0c4bab
Binary files /dev/null and b/tim/prune/gui/images/wpicon_plectrum_s.png differ
diff --git a/tim/prune/gui/images/wpicon_ring_l.png b/tim/prune/gui/images/wpicon_ring_l.png
new file mode 100644 (file)
index 0000000..66b190d
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ring_l.png differ
diff --git a/tim/prune/gui/images/wpicon_ring_m.png b/tim/prune/gui/images/wpicon_ring_m.png
new file mode 100644 (file)
index 0000000..3ae352b
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ring_m.png differ
diff --git a/tim/prune/gui/images/wpicon_ring_s.png b/tim/prune/gui/images/wpicon_ring_s.png
new file mode 100644 (file)
index 0000000..bd7dfb3
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ring_s.png differ
diff --git a/tim/prune/gui/images/wpicon_ringpt_l.png b/tim/prune/gui/images/wpicon_ringpt_l.png
new file mode 100644 (file)
index 0000000..9a6b57e
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ringpt_l.png differ
diff --git a/tim/prune/gui/images/wpicon_ringpt_m.png b/tim/prune/gui/images/wpicon_ringpt_m.png
new file mode 100644 (file)
index 0000000..fabfe84
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ringpt_m.png differ
diff --git a/tim/prune/gui/images/wpicon_ringpt_s.png b/tim/prune/gui/images/wpicon_ringpt_s.png
new file mode 100644 (file)
index 0000000..ac16f60
Binary files /dev/null and b/tim/prune/gui/images/wpicon_ringpt_s.png differ
index 57119517dec55f7f7a9f964b08460872f6d32981..801adbb0137e61575d7d00d5468a4e115425c867 100644 (file)
@@ -1,39 +1,10 @@
 package tim.prune.gui.map;
 
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Cursor;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.RenderingHints;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.MouseMotionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
+import java.awt.*;
+import java.awt.event.*;
 import java.awt.image.BufferedImage;
 
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JMenuItem;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JSlider;
-import javax.swing.SwingUtilities;
+import javax.swing.*;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
@@ -44,23 +15,12 @@ import tim.prune.I18nManager;
 import tim.prune.UpdateMessageBroker;
 import tim.prune.config.ColourScheme;
 import tim.prune.config.Config;
-import tim.prune.data.Checker;
-import tim.prune.data.Coordinate;
-import tim.prune.data.DataPoint;
-import tim.prune.data.DoubleRange;
-import tim.prune.data.Field;
-import tim.prune.data.FieldList;
-import tim.prune.data.Latitude;
-import tim.prune.data.Longitude;
-import tim.prune.data.MidpointData;
-import tim.prune.data.Selection;
-import tim.prune.data.Track;
-import tim.prune.data.TrackInfo;
+import tim.prune.data.*;
 import tim.prune.function.compress.MarkPointsInRectangleFunction;
 import tim.prune.function.edit.FieldEdit;
 import tim.prune.function.edit.FieldEditList;
 import tim.prune.gui.IconManager;
-import tim.prune.gui.TripleStateCheckBox;
+import tim.prune.gui.MultiStateCheckBox;
 import tim.prune.gui.colour.PointColourer;
 import tim.prune.tips.TipManager;
 
@@ -99,7 +59,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        /** Checkbox for autopan */
        private JCheckBox _autopanCheckBox = null;
        /** Checkbox for connecting track points */
-       private TripleStateCheckBox _connectCheckBox = null;
+       private MultiStateCheckBox _connectCheckBox = null;
        /** Checkbox for enable edit mode */
        private JCheckBox _editmodeCheckBox = null;
        /** Right-click popup menu */
@@ -128,6 +88,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        private boolean _shownOsmErrorAlready = false;
        /** Current drawing mode */
        private int _drawMode = MODE_DEFAULT;
+       /** Current waypoint icon definition */
+       WpIconDefinition _waypointIconDefinition = null;
 
        /** Constant for click sensitivity when selecting nearest point */
        private static final int CLICK_SENSITIVITY = 10;
@@ -243,10 +205,11 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                _autopanCheckBox.setFocusable(false); // stop button from stealing keyboard focus
                _topPanel.add(_autopanCheckBox);
                // Add checkbox button for connecting points or not
-               _connectCheckBox = new TripleStateCheckBox();
-               _connectCheckBox.setIcon(0, IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
-               _connectCheckBox.setIcon(1, IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON));
-               _connectCheckBox.setIcon(2, IconManager.getImageIcon(IconManager.POINTS_HIDDEN_BUTTON));
+               _connectCheckBox = new MultiStateCheckBox(4);
+               _connectCheckBox.setIcon(0, IconManager.getImageIcon(IconManager.POINTS_WITH_ARROWS_BUTTON));
+               _connectCheckBox.setIcon(1, IconManager.getImageIcon(IconManager.POINTS_HIDDEN_BUTTON));
+               _connectCheckBox.setIcon(2, IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
+               _connectCheckBox.setIcon(3, IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON));
                _connectCheckBox.setCurrentState(0);
                _connectCheckBox.setOpaque(false);
                _connectCheckBox.setToolTipText(I18nManager.getText("menu.map.connect"));
@@ -305,6 +268,8 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                makePopup();
                // Get currently selected map from Config, pass to MapTileManager
                _tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
+               // Update display settings
+               dataUpdated(MAPSERVER_CHANGED);
        }
 
 
@@ -409,7 +374,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        }
 
                        // Draw the map contents if necessary
-                       if ((_mapImage == null || _recalculate))
+                       if (_mapImage == null || _recalculate)
                        {
                                paintMapContents();
                                _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
@@ -601,7 +566,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                if (trackOpacity > 0.0f)
                {
                        // Paint the track points on top
-                       int pointsPainted = 1;
+                       boolean pointsPainted = true;
                        try
                        {
                                if (trackOpacity > 0.9f)
@@ -632,7 +597,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
 
                        // Zoom to fit if no points found
-                       if (pointsPainted <= 0 && _checkBounds)
+                       if (!pointsPainted && _checkBounds)
                        {
                                zoomToFit();
                                _recalculate = true;
@@ -652,9 +617,9 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
        /**
         * Paint the points using the given graphics object
         * @param inG Graphics object to use for painting
-        * @return number of points painted, if any
+        * @return true if any points or lines painted
         */
-       private int paintPoints(Graphics inG)
+       private boolean paintPoints(Graphics inG)
        {
                // Set up colours
                final ColourScheme cs = Config.getColourScheme();
@@ -674,6 +639,9 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                final int[] xPixels = new int[numPoints];
                final int[] yPixels = new int[numPoints];
 
+               final int pointSeparationForArrowsSqd = 350;
+               final int pointSeparation1dForArrows = (int) (Math.sqrt(pointSeparationForArrowsSqd) * 0.7);
+
                // try to set line width for painting
                if (inG instanceof Graphics2D)
                {
@@ -681,16 +649,20 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                        if (lineWidth < 1 || lineWidth > 4) {lineWidth = 2;}
                        ((Graphics2D) inG).setStroke(new BasicStroke(lineWidth));
                }
-               int pointsPainted = 0;
+
+               boolean pointsPainted = false;
                // draw track points
                inG.setColor(pointColour);
                int prevX = -1, prevY = -1;
                final int connectState = _connectCheckBox.getCurrentState();
-               final boolean drawLines = (connectState % 2) == 0; // 0 or 2
-               final boolean drawPoints = (connectState <= 1);    // 0 or 1
+               final boolean drawLines = (connectState != 3);  // 0, 1 or 2
+               final boolean drawPoints = (connectState != 1); // 0, 2 or 3
+               final boolean drawArrows = (connectState == 0); // 0
+
                boolean prevPointVisible = false, currPointVisible = false;
                boolean anyWaypoints = false;
                boolean isWaypoint = false;
+               boolean drawnLastArrow = false; // avoid painting arrows on adjacent lines, looks too busy
                for (int i=0; i<numPoints; i++)
                {
                        // Calculate pixel position of point from its x, y coordinates
@@ -722,18 +694,55 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                        }
 
                                        // Draw rectangle for track point if it's visible
-                                       if (currPointVisible && drawPoints)
+                                       if (currPointVisible)
                                        {
-                                               inG.drawRect(px-2, py-2, 3, 3);
-                                               pointsPainted++;
+                                               if (drawPoints) {
+                                                       inG.drawRect(px-2, py-2, 3, 3);
+                                               }
+                                               pointsPainted = true;
                                        }
                                }
 
                                // Connect track points if either of them are visible
-                               if (drawLines && !(prevX == -1 && prevY == -1)
+                               if (drawLines
+                                && (currPointVisible || prevPointVisible)
+                                && !(prevX == -1 && prevY == -1)
                                 && !_track.getPoint(i).getSegmentStart())
                                {
                                        inG.drawLine(prevX, prevY, px, py);
+                                       pointsPainted = true;
+
+                                       // Now consider whether we need to draw an arrow as well
+                                       if (drawArrows
+                                        && !drawnLastArrow
+                                        && (Math.abs(prevX-px) > pointSeparation1dForArrows || Math.abs(prevY-py) > pointSeparation1dForArrows))
+                                       {
+                                               final double pointSeparationSqd = (prevX-px) * (prevX-px) + (prevY-py) * (prevY-py);
+                                               if (pointSeparationSqd > pointSeparationForArrowsSqd)
+                                               {
+                                                       final double midX = (prevX + px) / 2;
+                                                       final double midY = (prevY + py) / 2;
+                                                       final boolean midPointVisible = midX >= 0 && midX < winWidth && midY >= 0 && midY < winHeight;
+                                                       if (midPointVisible)
+                                                       {
+                                                               final double alpha = Math.atan2(py - prevY, px - prevX);
+                                                               //System.out.println("Draw arrow from (" + prevX + "," + prevY + ") to (" + px + "," + py
+                                                               //      + ") with angle" + (int) (alpha * 180/Math.PI));
+                                                               final double MID_TO_VERTEX = 3.0;
+                                                               final double arrowX = MID_TO_VERTEX * Math.cos(alpha);
+                                                               final double arrowY = MID_TO_VERTEX * Math.sin(alpha);
+                                                               final double vertexX = midX + arrowX;
+                                                               final double vertexY = midY + arrowY;
+                                                               inG.drawLine((int)(midX-arrowX-2*arrowY), (int)(midY-arrowY+2*arrowX), (int)vertexX, (int)vertexY);
+                                                               inG.drawLine((int)(midX-arrowX+2*arrowY), (int)(midY-arrowY-2*arrowX), (int)vertexX, (int)vertexY);
+                                                       }
+                                                       drawnLastArrow = midPointVisible;
+                                               }
+                                       }
+                                       else
+                                       {
+                                               drawnLastArrow = false;
+                                       }
                                }
                                prevX = px; prevY = py;
                        }
@@ -755,8 +764,20 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                        int py = yPixels[i];
                                        if (px >= 0 && px < winWidth && py >= 0 && py < winHeight)
                                        {
-                                               inG.fillRect(px-3, py-3, 6, 6);
-                                               pointsPainted++;
+                                               if (_waypointIconDefinition == null)
+                                               {
+                                                       inG.fillRect(px-3, py-3, 6, 6);
+                                               }
+                                               else
+                                               {
+                                                       ImageIcon icon = _waypointIconDefinition.getImageIcon();
+                                                       if (icon != null)
+                                                       {
+                                                               inG.drawImage(icon.getImage(), px-_waypointIconDefinition.getXOffset(),
+                                                                       py-_waypointIconDefinition.getYOffset(), null);
+                                                       }
+                                               }
+                                               pointsPainted = true;
                                                numWaypoints++;
                                        }
                                }
@@ -820,7 +841,7 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                                {
                                        inG.drawRect(px-1, py-1, 2, 2);
                                        inG.drawRect(px-2, py-2, 4, 4);
-                                       pointsPainted++;
+                                       pointsPainted = true;
                                }
                        }
                }
@@ -1354,9 +1375,20 @@ public class MapCanvas extends JPanel implements MouseListener, MouseMotionListe
                if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) {
                        _checkBounds = true;
                }
-               if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
+               if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0)
+               {
                        // Get the selected map source index and pass to tile manager
                        _tileManager.setMapSource(Config.getConfigInt(Config.KEY_MAPSOURCE_INDEX));
+                       final int wpType = Config.getConfigInt(Config.KEY_WAYPOINT_ICONS);
+                       if (wpType == WpIconLibrary.WAYPT_DEFAULT)
+                       {
+                               _waypointIconDefinition = null;
+                       }
+                       else
+                       {
+                               final int wpSize = Config.getConfigInt(Config.KEY_WAYPOINT_ICON_SIZE);
+                               _waypointIconDefinition = WpIconLibrary.getIconDefinition(wpType, wpSize);
+                       }
                }
                if ((inUpdateType & (DataSubscriber.DATA_ADDED_OR_REMOVED + DataSubscriber.DATA_EDITED)) > 0) {
                        _midpoints.updateData(_track);
index e324af1a029114e0e726f19bf3baabb8448f096f..5f44d5e64d4b757b012667cd2781c912cef325f9 100644 (file)
@@ -38,14 +38,20 @@ public abstract class MapSourceLibrary
         */
        private static void addFixedSources()
        {
+               final String THUNDERFOREST_APIKEY = "c32212f10b13496786b98dc6c42e5c3c";
                _sourceList.add(new OsmMapSource("Mapnik", "http://[abc].tile.openstreetmap.org/"));
-               _sourceList.add(new OsmMapSource("Cyclemap", "http://[abc].tile.opencyclemap.org/cycle/"));
+               OsmMapSource cycleSource = new OsmMapSource("OpenCycleMap", "http://[abc].tile.thunderforest.com/cycle/");
+               cycleSource.setApiKey(THUNDERFOREST_APIKEY);
+               _sourceList.add(cycleSource);
+               OsmMapSource outdoorsSource = new OsmMapSource("Outdoors", "http://[abc].tile.thunderforest.com/outdoors/");
+               outdoorsSource.setApiKey(THUNDERFOREST_APIKEY);
+               _sourceList.add(outdoorsSource);
                _sourceList.add(new OsmMapSource("Reitkarte", "http://topo[234].wanderreitkarte.de/topo/"));
                _sourceList.add(new MffMapSource("Mapsforfree", "http://maps-for-free.com/layer/relief/", "jpg",
                        "http://maps-for-free.com/layer/water/", "gif", 11));
                _sourceList.add(new OsmMapSource("Hikebikemap", "http://[abc].tiles.wmflabs.org/hikebike/",
                        "http://[abc].tiles.wmflabs.org/hillshading/", 18));
-               _sourceList.add(new OsmMapSource("Openseamap", "http://tile.openstreetmap.org/",
+               _sourceList.add(new OsmMapSource("OpenSeaMap", "http://tile.openstreetmap.org/",
                        "http://tiles.openseamap.org/seamark/", 18));
        }
 
index 070bb258510c578cd7e102863017408b2727093c..925fcf623ea456154c512c5e5e89454eb5b46bf8 100644 (file)
@@ -19,6 +19,9 @@ public class OsmMapSource extends MapSource
        private String[] _siteNames = null;
        /** Maximum zoom level */
        private int _maxZoom = 0;
+       /** API key, usually remains empty */
+       private String _apiKey = null;
+
 
        /**
         * Constructor giving single name and url
@@ -93,6 +96,12 @@ public class OsmMapSource extends MapSource
                _maxZoom = inMaxZoom;
        }
 
+       /** Set the API key (if required) */
+       public void setApiKey(String inKey)
+       {
+               _apiKey = inKey;
+       }
+
        /**
         * Construct a new map source from its config string
         * @param inConfigString string from Config, separated by semicolons
@@ -147,8 +156,15 @@ public class OsmMapSource extends MapSource
        public String makeURL(int inLayerNum, int inZoom, int inX, int inY)
        {
                // Check if the base url has a [1234], if so replace at random
-               return pickServerUrl(_baseUrls[inLayerNum])
-                       + inZoom + "/" + inX + "/" + inY + "." + getFileExtension(inLayerNum);
+               StringBuffer url = new StringBuffer();
+               url.append(pickServerUrl(_baseUrls[inLayerNum]));
+               url.append(inZoom).append('/').append(inX).append('/').append(inY);
+               url.append('.').append(getFileExtension(inLayerNum));
+               if (_apiKey != null)
+               {
+                       url.append("?apikey=").append(_apiKey);
+               }
+               return url.toString();
        }
 
        /**
diff --git a/tim/prune/gui/map/WpIconDefinition.java b/tim/prune/gui/map/WpIconDefinition.java
new file mode 100644 (file)
index 0000000..36e06f1
--- /dev/null
@@ -0,0 +1,48 @@
+package tim.prune.gui.map;
+
+import javax.swing.ImageIcon;
+
+/**
+ * Definition of a waypoint icon including name and offsets
+ */
+public class WpIconDefinition
+{
+       /** Name of icon, used for finding image file */
+       private final String _name;
+       /** X offset of marker point in image */
+       private final int    _xOffset;
+       /** Y offset of marker point in image */
+       private final int    _yOffset;
+       /** icon */
+       private ImageIcon _icon = null;
+
+
+       /**
+        * Constructor
+        * @param inName name of icon
+        * @param inX x offset
+        * @param inY y offset
+        */
+       public WpIconDefinition(String inName, int inX, int inY)
+       {
+               _name = inName;
+               _xOffset = inX;
+               _yOffset = inY;
+       }
+
+       /** @return name of icon */
+       public String getName() {return _name;}
+       /** @return x offset */
+       public int getXOffset() {return _xOffset;}
+       /** @return y offset */
+       public int getYOffset() {return _yOffset;}
+
+       /** @param inIcon icon to set */
+       public void setIcon(ImageIcon inIcon) {_icon = inIcon;}
+
+       /** @return image icon to display */
+       public ImageIcon getImageIcon()
+       {
+               return _icon;
+       }
+}
diff --git a/tim/prune/gui/map/WpIconLibrary.java b/tim/prune/gui/map/WpIconLibrary.java
new file mode 100644 (file)
index 0000000..e391d91
--- /dev/null
@@ -0,0 +1,92 @@
+package tim.prune.gui.map;
+
+import javax.swing.ImageIcon;
+
+import tim.prune.gui.IconManager;
+
+/**
+ * Class to provide a library of waypoint icon definitions
+ */
+public abstract class WpIconLibrary
+{
+       /** Types of waypoint */
+       public static final int WAYPT_DEFAULT = 0;
+       public static final int WAYPT_RING_POINT = 1;
+       public static final int WAYPT_PLECTRUM = 2;
+       public static final int WAYPT_CIRCLE = 3;
+       public static final int WAYPT_PIN = 4;
+       public static final int WAYPT_NUMBER_OF_ICONS = WAYPT_PIN + 1;
+
+       /** Sizes of icon */
+       public static final int SIZE_SMALL = 0;
+       public static final int SIZE_MEDIUM = 1;
+       public static final int SIZE_LARGE = 2;
+
+       /** Array of x and y offsets for the icons */
+       private static int[] _PIXEL_OFFSETS = null;
+
+       /** Static block to initialise offsets */
+       static
+       {
+               _PIXEL_OFFSETS = new int[] {0, 0, 0, 0, 0, 0, // default
+                       8,  13, 12, 22, 14, 26, // ringpt
+                       7,  15, 12, 24, 14, 27, // plectrum
+                       8,  8,  12, 12, 14, 14, // ring
+                       2,  15, 4,  23, 4,  27  // pin
+               };
+       }
+
+       /** @return array of Integers representing waypoint types */
+       public static Integer[] getWaypointTypes()
+       {
+               return new Integer[] {WAYPT_DEFAULT, WAYPT_RING_POINT, WAYPT_PLECTRUM, WAYPT_CIRCLE, WAYPT_PIN};
+       }
+
+       /**
+        * @param inType icon type
+        * @return the name of the specified icon, used for settings dialog
+        */
+       public static String getIconName(int inType)
+       {
+               switch (inType)
+               {
+                       case WAYPT_RING_POINT: return "ringpt";
+                       case WAYPT_PLECTRUM:   return "plectrum";
+                       case WAYPT_CIRCLE:     return "ring";
+                       case WAYPT_PIN:        return "pin";
+                       case WAYPT_DEFAULT:
+                       default:               return "default";
+               }
+       }
+
+       /**
+        * @param inType icon type
+        * @param inSize icon size (small/medium/large)
+        * @return icon definition for the specified icon
+        */
+       public static WpIconDefinition getIconDefinition(int inType, int inSize)
+       {
+               String iconName = getIconName(inType);
+               String sizeSuffix = null;
+               switch (inSize)
+               {
+                       case SIZE_SMALL:  sizeSuffix = "_s"; break;
+                       case SIZE_MEDIUM: sizeSuffix = "_m"; break;
+                       case SIZE_LARGE:  sizeSuffix = "_l"; break;
+                       default:          sizeSuffix = "_m"; inSize = SIZE_MEDIUM; break;
+               }
+               // Look up offsets in the static array
+               int xOffset = 0, yOffset = 0;
+               try {
+                       xOffset = _PIXEL_OFFSETS[inType * 6 + inSize * 2];
+                       yOffset = _PIXEL_OFFSETS[inType * 6 + inSize * 2 + 1];
+               }
+               catch (ArrayIndexOutOfBoundsException obe) {} // ignore, leave offsets at 0
+               WpIconDefinition iconDef = new WpIconDefinition(iconName, xOffset, yOffset);
+               // Get icon
+               ImageIcon icon = IconManager.getImageIcon(IconManager.WAYPOINT_ICON_PREFIX
+                       + iconDef.getName() + sizeSuffix + IconManager.WAYPOINT_ICON_SUFFIX);
+               iconDef.setIcon(icon);
+               return iconDef;
+       }
+}
diff --git a/tim/prune/jpeg/ExifGateway.java b/tim/prune/jpeg/ExifGateway.java
deleted file mode 100644 (file)
index c11ba2e..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-package tim.prune.jpeg;
-
-import java.io.File;
-import javax.swing.JOptionPane;
-import tim.prune.I18nManager;
-
-/**
- * Skeleton gateway to the Exif functions.
- * This is required by Debian to divert Exif handling
- * to the external libmetadata-extractor-java library
- * instead of the included modified routines.
- *
- * Switching between internal and external libraries is
- * handled by the ExifLibrarySwitch
- */
-public abstract class ExifGateway
-{
-       /** Library object to call */
-       private static ExifLibrary _exifLibrary = null;
-       /** Flag to set whether failure warning has already been shown */
-       private static boolean _exifFailWarned = false;
-
-       /** Static block to initialise library */
-       static
-       {
-               String libraryClass = ExifLibrarySwitch.USE_INTERNAL_LIBRARY?"InternalExifLibrary":"ExternalExifLibrary";
-               try
-               {
-                       _exifLibrary = (ExifLibrary) Class.forName("tim.prune.jpeg." + libraryClass).newInstance();
-               }
-               catch (Throwable nolib) {_exifLibrary = null;}
-       }
-
-
-       /**
-        * Get the Jpeg data from the given file
-        * @param inFile file to read
-        * @return jpeg data, or null if none found
-        */
-       public static JpegData getJpegData(File inFile)
-       {
-               try
-               {
-                       // Call library (if found)
-                       if (_exifLibrary != null) {
-                               JpegData data = _exifLibrary.getJpegData(inFile);
-                               return data;
-                       }
-               }
-               catch (LinkageError nolib) {
-                       System.err.println("Link: " + nolib.getMessage());
-                       nolib.printStackTrace();
-               }
-               // Not successful - warn if necessary
-               if (!_exifFailWarned)
-               {
-                       JOptionPane.showMessageDialog(null, I18nManager.getText("error.jpegload.exifreadfailed"),
-                               I18nManager.getText("error.jpegload.dialogtitle"), JOptionPane.WARNING_MESSAGE);
-                       _exifFailWarned = true;
-               }
-               return null;
-       }
-
-       /**
-        * @return key to use to describe library, matching key for about dialog
-        */
-       public static String getDescriptionKey()
-       {
-               String key = ExifLibrarySwitch.USE_INTERNAL_LIBRARY?"internal":"external";
-               if (_exifLibrary == null || !_exifLibrary.looksOK()) {key = key + ".failed";}
-               return key;
-       }
-
-
-       /**
-        * @param inNumerator numerator from Rational
-        * @param inDenominator denominator from Rational
-        * @return the value of the specified number as a positive <code>double</code>.
-        * Prevents interpretation of 32 bit numbers as negative, and forces a positive answer
-        */
-       public static final double convertToPositiveValue(int inNumerator, int inDenominator)
-       {
-               if (inDenominator == 0) return 0.0;
-               double numeratorDbl = inNumerator;
-               double denomDbl = inDenominator;
-               if (inNumerator >= 0)
-                       return numeratorDbl / denomDbl;
-               final double correction = Math.pow(2.0, 32);
-               numeratorDbl += correction;
-               if (inDenominator < 0) denomDbl += correction;
-               return numeratorDbl / denomDbl;
-       }
-
-
-       /**
-        * @param inNumerator numerator from Rational
-        * @param inDenominator denominator from Rational
-        * @return the value of the specified number as a positive <code>double</code>.
-        * Forces a positive answer
-        */
-       public static final double convertToPositiveValue(long inNumerator, long inDenominator)
-       {
-               if (inDenominator == 0L) return 0.0;
-               final double numeratorDbl = inNumerator;
-               final double denomDbl = inDenominator;
-               return numeratorDbl / denomDbl;
-       }
-}
diff --git a/tim/prune/jpeg/ExifLibrary.java b/tim/prune/jpeg/ExifLibrary.java
deleted file mode 100644 (file)
index 8d00617..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package tim.prune.jpeg;
-
-import java.io.File;
-
-/**
- * Interface satisfied by both internal and external Exif implementations
- */
-public interface ExifLibrary
-{
-       /** Get the Jpeg data from the given file */
-       public JpegData getJpegData(File inFile);
-
-       /** Check that dependencies are resolved */
-       public boolean looksOK();
-}
diff --git a/tim/prune/jpeg/ExifLibrarySwitch.java b/tim/prune/jpeg/ExifLibrarySwitch.java
deleted file mode 100644 (file)
index 01adce1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package tim.prune.jpeg;
-
-/**
- * Empty class just to provide a switch between internal and external libraries
- */
-public abstract class ExifLibrarySwitch
-{
-       /** Flag to specify internal or external library */
-       static final boolean USE_INTERNAL_LIBRARY = true;
-}
diff --git a/tim/prune/jpeg/ExternalExifLibrary.java b/tim/prune/jpeg/ExternalExifLibrary.java
deleted file mode 100644 (file)
index 4d06a27..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-package tim.prune.jpeg;
-
-import java.io.File;
-
-import com.drew.imaging.ImageMetadataReader;
-import com.drew.lang.Rational;
-import com.drew.metadata.Directory;
-import com.drew.metadata.Metadata;
-import com.drew.metadata.exif.ExifSubIFDDirectory;
-import com.drew.metadata.exif.ExifIFD0Directory;
-import com.drew.metadata.exif.ExifReader;
-import com.drew.metadata.exif.ExifThumbnailDirectory;
-import com.drew.metadata.exif.GpsDirectory;
-
-/**
- * Class to act as a gateway into the external exif library functions.
- * This should be the only class with dependence on the lib-metadata-extractor-java
- * classes (which are NOT delivered with GpsPrune).
- * This class will not compile without this extra dependency (but is not required if
- * the ExifGateway uses the InternalExifLibrary instead).
- * Should not be included if the internal library will be used (from jpeg.drew package).
- */
-public class ExternalExifLibrary implements ExifLibrary
-{
-       /**
-        * Use the _external_ exif library to get the data from the given file
-        * @param inFile file to access
-        * @return Jpeg data if available, otherwise null
-        */
-       public JpegData getJpegData(File inFile)
-       {
-               JpegData data = new JpegData();
-               // Read exif data from picture
-               try
-               {
-                       Metadata metadata = ImageMetadataReader.readMetadata(inFile);
-                       if (metadata.containsDirectory(GpsDirectory.class))
-                       {
-                               Directory gpsdir = metadata.getDirectory(GpsDirectory.class);
-                               if (gpsdir.containsTag(GpsDirectory.TAG_LATITUDE)
-                                       && gpsdir.containsTag(GpsDirectory.TAG_LONGITUDE)
-                                       && gpsdir.containsTag(GpsDirectory.TAG_LATITUDE_REF)
-                                       && gpsdir.containsTag(GpsDirectory.TAG_LONGITUDE_REF))
-                               {
-                                       data.setLatitudeRef(gpsdir.getString(GpsDirectory.TAG_LATITUDE_REF));
-                                       Rational[] latRats = gpsdir.getRationalArray(GpsDirectory.TAG_LATITUDE);
-                                       double seconds = ExifGateway.convertToPositiveValue(latRats[2].getNumerator(), latRats[2].getDenominator());
-                                       data.setLatitude(new double[] {latRats[0].doubleValue(),
-                                               latRats[1].doubleValue(), seconds});
-                                       data.setLongitudeRef(gpsdir.getString(GpsDirectory.TAG_LONGITUDE_REF));
-                                       Rational[] lonRats = gpsdir.getRationalArray(GpsDirectory.TAG_LONGITUDE);
-                                       seconds = ExifGateway.convertToPositiveValue(lonRats[2].getNumerator(), lonRats[2].getDenominator());
-                                       data.setLongitude(new double[] {lonRats[0].doubleValue(),
-                                               lonRats[1].doubleValue(), seconds});
-                               }
-
-                               // Altitude (if present)
-                               if (gpsdir.containsTag(GpsDirectory.TAG_ALTITUDE) && gpsdir.containsTag(GpsDirectory.TAG_ALTITUDE_REF))
-                               {
-                                       data.setAltitude(gpsdir.getRational(GpsDirectory.TAG_ALTITUDE).intValue());
-                                       byte altRef = (byte) gpsdir.getInt(GpsDirectory.TAG_ALTITUDE_REF);
-                                       data.setAltitudeRef(altRef);
-                               }
-
-                               // Timestamp and datestamp (if present)
-                               final int TAG_DATESTAMP = 0x001d;
-                               if (gpsdir.containsTag(GpsDirectory.TAG_TIME_STAMP) && gpsdir.containsTag(TAG_DATESTAMP))
-                               {
-                                       Rational[] times = gpsdir.getRationalArray(GpsDirectory.TAG_TIME_STAMP);
-                                       data.setGpsTimestamp(new int[] {times[0].intValue(), times[1].intValue(),
-                                               times[2].intValue()});
-                                       Rational[] dates = gpsdir.getRationalArray(TAG_DATESTAMP);
-                                       if (dates != null) {
-                                               data.setGpsDatestamp(new int[] {dates[0].intValue(), dates[1].intValue(), dates[2].intValue()});
-                                       }
-                               }
-
-                               // Image bearing (if present)
-                               if (gpsdir.containsTag(GpsDirectory.TAG_IMG_DIRECTION) && gpsdir.containsTag(GpsDirectory.TAG_IMG_DIRECTION_REF))
-                               {
-                                       Rational bearing = gpsdir.getRational(GpsDirectory.TAG_IMG_DIRECTION);
-                                       if (bearing != null) {
-                                               data.setBearing(bearing.doubleValue());
-                                       }
-                               }
-                       }
-
-                       // Tags from Exif directory
-                       if (metadata.containsDirectory(ExifSubIFDDirectory.class))
-                       {
-                               Directory exifdir = metadata.getDirectory(ExifSubIFDDirectory.class);
-
-                               // Take time and date from exif tags
-                               if (exifdir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
-                                       data.setOriginalTimestamp(exifdir.getString(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL));
-                               }
-                               // Also take "digitized" timestamp
-                               if (exifdir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED)) {
-                                       data.setDigitizedTimestamp(exifdir.getString(ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED));
-                               }
-                       }
-                       if (metadata.containsDirectory(ExifIFD0Directory.class))
-                       {
-                               Directory exifdir = metadata.getDirectory(ExifIFD0Directory.class);
-
-                               // Photo rotation code
-                               if (exifdir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
-                                       data.setOrientationCode(exifdir.getInt(ExifIFD0Directory.TAG_ORIENTATION));
-                                       // NOTE: this presumably takes the _last_ orientation value found, not the first.
-                               }
-                       }
-
-                       if (metadata.containsDirectory(ExifThumbnailDirectory.class))
-                       {
-                               ExifThumbnailDirectory exifdir = metadata.getDirectory(ExifThumbnailDirectory.class);
-
-                               // TODO: Check this thumbnail stuff
-                               if (exifdir.hasThumbnailData())
-                               {
-                                       // Make a copy of the byte data
-                                       byte[] tdata = exifdir.getThumbnailData();
-                                       byte[] thumb = new byte[tdata.length];
-                                       System.arraycopy(tdata, 0, thumb, 0, tdata.length);
-                                       data.setThumbnailImage(thumb);
-                               }
-                       }
-
-               }
-               catch (Exception e) {
-                       // Exception reading metadata, just ignore it
-                       //System.err.println("Error: " + e.getClass().getName() + " - " + e.getMessage());
-               }
-               return data;
-       }
-
-
-       /**
-        * Check whether the exifreader class can be correctly resolved
-        * @return true if it looks ok
-        */
-       public boolean looksOK()
-       {
-               try {
-                       String test = ExifReader.class.getName();
-                       if (test != null) return true;
-               }
-               catch (LinkageError le) {}
-               return false;
-       }
-}
index c8b17089990bc405ada9492ab06bdc3937674200..377155fd2f862fc1a30f040fda7a16c52273b505 100644 (file)
@@ -1,15 +1,15 @@
 package tim.prune.jpeg;
 
 import java.io.File;
+
 import tim.prune.jpeg.drew.ExifReader;
-import tim.prune.jpeg.drew.JpegException;
+import tim.prune.jpeg.drew.ExifException;
 
 /**
- * Class to act as a gateway into the internal exif library functions.
+ * Class to act as an entry point to the internal exif library functions.
  * This should be the only class with dependence on the jpeg.drew package.
- * Should not be included if external library will be used (eg Debian).
  */
-public class InternalExifLibrary implements ExifLibrary
+public class InternalExifLibrary
 {
        /**
         * Use the _internal_ exif library to get the data from the given file
@@ -20,23 +20,9 @@ public class InternalExifLibrary implements ExifLibrary
        {
                JpegData data = null;
                try {
-                       data = new ExifReader(inFile).extract();
+                       data = ExifReader.readMetadata(inFile);
                }
-               catch (JpegException jpe) {} // data remains null
+               catch (ExifException jpe) {} // data remains null
                return data;
        }
-
-       /**
-        * Check whether the exifreader class can be correctly resolved
-        * @return true if it looks ok
-        */
-       public boolean looksOK()
-       {
-               try {
-                       String test = ExifReader.class.getName();
-                       if (test != null) return true;
-               }
-               catch (LinkageError le) {}
-               return false;
-       }
 }
diff --git a/tim/prune/jpeg/drew/ByteArrayReader.java b/tim/prune/jpeg/drew/ByteArrayReader.java
new file mode 100644 (file)
index 0000000..3b3d010
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+
+package tim.prune.jpeg.drew;
+
+import java.io.IOException;
+
+
+/**
+ * Provides methods to read specific values from a byte array,
+ * with a consistent, checked exception structure for issues.
+ *
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class ByteArrayReader
+{
+       private final byte[] _buffer;
+       private boolean _isMotorolaByteOrder = true;
+
+       public ByteArrayReader(byte[] buffer)
+       {
+               if (buffer == null)
+                       throw new NullPointerException();
+
+               _buffer = buffer;
+       }
+
+       public void setMotorolaByteOrder(boolean motorolaByteOrder)
+       {
+               _isMotorolaByteOrder = motorolaByteOrder;
+       }
+
+       public long getLength()
+       {
+               return _buffer.length;
+       }
+
+       protected byte getByte(int index)
+       {
+               return _buffer[index];
+       }
+
+       protected void validateIndex(int index, int bytesRequested) throws ExifException
+       {
+               if (!isValidIndex(index, bytesRequested))
+                       throw new ExifException("Invalid index " + index);
+       }
+
+       private boolean isValidIndex(int index, int bytesRequested)
+       {
+               return bytesRequested >= 0
+                       && index >= 0
+                       && ((long)index + (long)bytesRequested) <= (long)_buffer.length;
+       }
+
+       public byte[] getBytes(int index, int count) throws ExifException
+       {
+               validateIndex(index, count);
+
+               byte[] bytes = new byte[count];
+               System.arraycopy(_buffer, index, bytes, 0, count);
+               return bytes;
+       }
+
+       /**
+        * Returns an unsigned 8-bit int calculated from one byte of data at the specified index.
+        *
+        * @param index position within the data buffer to read byte
+        * @return the 8 bit int value, between 0 and 255
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public short getUInt8(int index) throws ExifException
+       {
+               validateIndex(index, 1);
+
+               return (short) (getByte(index) & 0xFF);
+       }
+
+       /**
+        * Returns a signed 8-bit int calculated from one byte of data at the specified index.
+        *
+        * @param index position within the data buffer to read byte
+        * @return the 8 bit int value, between 0x00 and 0xFF
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public byte getInt8(int index) throws ExifException
+       {
+               validateIndex(index, 1);
+
+               return getByte(index);
+       }
+
+       /**
+        * Returns an unsigned 16-bit int calculated from two bytes of data at the specified index.
+        *
+        * @param index position within the data buffer to read first byte
+        * @return the 16 bit int value, between 0x0000 and 0xFFFF
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public int getUInt16(int index) throws ExifException
+       {
+               validateIndex(index, 2);
+
+               if (_isMotorolaByteOrder) {
+                       // Motorola - MSB first
+                       return (getByte(index    ) << 8 & 0xFF00) |
+                                  (getByte(index + 1)      & 0xFF);
+               } else {
+                       // Intel ordering - LSB first
+                       return (getByte(index + 1) << 8 & 0xFF00) |
+                                  (getByte(index    )      & 0xFF);
+               }
+       }
+
+       /**
+        * Returns a signed 16-bit int calculated from two bytes of data at the specified index (MSB, LSB).
+        *
+        * @param index position within the data buffer to read first byte
+        * @return the 16 bit int value, between 0x0000 and 0xFFFF
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public short getInt16(int index) throws ExifException
+       {
+               validateIndex(index, 2);
+
+               if (_isMotorolaByteOrder) {
+                       // Motorola - MSB first
+                       return (short) (((short)getByte(index    ) << 8 & (short)0xFF00) |
+                                       ((short)getByte(index + 1)      & (short)0xFF));
+               } else {
+                       // Intel ordering - LSB first
+                       return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) |
+                                       ((short)getByte(index    )      & (short)0xFF));
+               }
+       }
+
+       /**
+        * Get a 32-bit unsigned integer from the buffer, returning it as a long.
+        *
+        * @param index position within the data buffer to read first byte
+        * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public long getUInt32(int index) throws ExifException
+       {
+               validateIndex(index, 4);
+
+               if (_isMotorolaByteOrder) {
+                       // Motorola - MSB first (big endian)
+                       return (((long)getByte(index    )) << 24 & 0xFF000000L) |
+                                  (((long)getByte(index + 1)) << 16 & 0xFF0000L) |
+                                  (((long)getByte(index + 2)) << 8  & 0xFF00L) |
+                                  (((long)getByte(index + 3))       & 0xFFL);
+               } else {
+                       // Intel ordering - LSB first (little endian)
+                       return (((long)getByte(index + 3)) << 24 & 0xFF000000L) |
+                                  (((long)getByte(index + 2)) << 16 & 0xFF0000L) |
+                                  (((long)getByte(index + 1)) << 8  & 0xFF00L) |
+                                  (((long)getByte(index    ))       & 0xFFL);
+               }
+       }
+
+       /**
+        * Returns a signed 32-bit integer from four bytes of data at the specified index the buffer.
+        *
+        * @param index position within the data buffer to read first byte
+        * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF
+        * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
+        */
+       public int getInt32(int index) throws ExifException
+       {
+               validateIndex(index, 4);
+
+               if (_isMotorolaByteOrder) {
+                       // Motorola - MSB first (big endian)
+                       return (getByte(index    ) << 24 & 0xFF000000) |
+                                  (getByte(index + 1) << 16 & 0xFF0000) |
+                                  (getByte(index + 2) << 8  & 0xFF00) |
+                                  (getByte(index + 3)       & 0xFF);
+               } else {
+                       // Intel ordering - LSB first (little endian)
+                       return (getByte(index + 3) << 24 & 0xFF000000) |
+                                  (getByte(index + 2) << 16 & 0xFF0000) |
+                                  (getByte(index + 1) << 8  & 0xFF00) |
+                                  (getByte(index    )       & 0xFF);
+               }
+       }
+
+       /**
+        * Creates a String from the _data buffer starting at the specified index,
+        * and ending where <code>byte=='\0'</code> or where <code>length==maxLength</code>.
+        *
+        * @param index          The index within the buffer at which to start reading the string.
+        * @param maxLengthBytes The maximum number of bytes to read.  If a zero-byte is not reached within this limit,
+        *                       reading will stop and the string will be truncated to this length.
+        * @return The read string.
+        * @throws IOException The buffer does not contain enough bytes to satisfy this request.
+        */
+       public String getNullTerminatedString(int index, int maxLengthBytes) throws ExifException
+       {
+               // NOTE currently only really suited to single-byte character strings
+
+               byte[] bytes = getBytes(index, maxLengthBytes);
+
+               // Count the number of non-null bytes
+               int length = 0;
+               while (length < bytes.length && bytes[length] != '\0')
+                       length++;
+
+               return new String(bytes, 0, length);
+       }
+
+       public String getString(int index, int bytesRequested) throws ExifException
+       {
+               // TODO: validate index
+               return new String(getBytes(index, bytesRequested));
+       }
+}
diff --git a/tim/prune/jpeg/drew/ExifException.java b/tim/prune/jpeg/drew/ExifException.java
new file mode 100644 (file)
index 0000000..6d26ba3
--- /dev/null
@@ -0,0 +1,12 @@
+package tim.prune.jpeg.drew;
+
+public class ExifException extends Exception
+{
+       public ExifException(String message) {
+               super(message);
+       }
+
+       public ExifException(Throwable cause) {
+               super(cause);
+       }
+}
index 4c90cabc687a6d7b0417632c36802cfba68134b6..c8c1b7c41bf4c0f8a2df9f240183226df1755a23 100644 (file)
-package tim.prune.jpeg.drew;\r
-\r
-import java.io.File;\r
-import java.util.HashMap;\r
-\r
-import tim.prune.jpeg.ExifGateway;\r
-import tim.prune.jpeg.JpegData;\r
-\r
-/**\r
- * Extracts Exif data from a JPEG header segment\r
- * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com\r
- * which in turn is based on code from Jhead http://www.sentex.net/~mwandel/jhead/\r
- */\r
-public class ExifReader\r
-{\r
-       /** The JPEG segment as an array of bytes */\r
-       private final byte[] _data;\r
-\r
-       /**\r
-        * Represents the native byte ordering used in the JPEG segment.  If true,\r
-        * then we're using Motorola ordering (Big endian), else we're using Intel\r
-        * ordering (Little endian).\r
-        */\r
-       private boolean _isMotorolaByteOrder;\r
-\r
-       /** Thumbnail offset */\r
-       private int _thumbnailOffset = -1;\r
-       /** Thumbnail length */\r
-       private int _thumbnailLength = -1;\r
-\r
-       /** The number of bytes used per format descriptor */\r
-       private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};\r
-\r
-       /** The number of formats known */\r
-       private static final int MAX_FORMAT_CODE = 12;\r
-\r
-       // Format types\r
-       // Note: Cannot use the DataFormat enumeration in the case statement that uses these tags.\r
-       //         Is there a better way?\r
-       //private static final int FMT_BYTE = 1;\r
-       private static final int FMT_STRING = 2;\r
-       //private static final int FMT_USHORT = 3;\r
-       //private static final int FMT_ULONG = 4;\r
-       private static final int FMT_URATIONAL = 5;\r
-       //private static final int FMT_SBYTE = 6;\r
-       //private static final int FMT_UNDEFINED = 7;\r
-       //private static final int FMT_SSHORT = 8;\r
-       //private static final int FMT_SLONG = 9;\r
-       private static final int FMT_SRATIONAL = 10;\r
-       //private static final int FMT_SINGLE = 11;\r
-       //private static final int FMT_DOUBLE = 12;\r
-\r
-       public static final int TAG_EXIF_OFFSET = 0x8769;\r
-       public static final int TAG_INTEROP_OFFSET = 0xA005;\r
-       public static final int TAG_GPS_INFO_OFFSET = 0x8825;\r
-       public static final int TAG_MAKER_NOTE = 0x927C;\r
-\r
-       public static final int TIFF_HEADER_START_OFFSET = 6;\r
-\r
-       /** GPS tag version GPSVersionID 0 0 BYTE 4 */\r
-       public static final int TAG_GPS_VERSION_ID = 0x0000;\r
-       /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */\r
-       public static final int TAG_GPS_LATITUDE_REF = 0x0001;\r
-       /** Latitude GPSLatitude 2 2 RATIONAL 3 */\r
-       public static final int TAG_GPS_LATITUDE = 0x0002;\r
-       /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */\r
-       public static final int TAG_GPS_LONGITUDE_REF = 0x0003;\r
-       /** Longitude GPSLongitude 4 4 RATIONAL 3 */\r
-       public static final int TAG_GPS_LONGITUDE = 0x0004;\r
-       /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */\r
-       public static final int TAG_GPS_ALTITUDE_REF = 0x0005;\r
-       /** Altitude GPSAltitude 6 6 RATIONAL 1 */\r
-       public static final int TAG_GPS_ALTITUDE = 0x0006;\r
-       /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */\r
-       public static final int TAG_GPS_TIMESTAMP = 0x0007;\r
-       /** GPS date (atomic clock) GPSDateStamp 23 1d RATIONAL 3 */\r
-       public static final int TAG_GPS_DATESTAMP = 0x001d;\r
-       /** "Original" Exif timestamp */\r
-       public static final int TAG_DATETIME_ORIGINAL = 0x9003;\r
-       /** "Creation" or "Digitized" timestamp */\r
-       public static final int TAG_DATETIME_DIGITIZED = 0x9004;\r
-       /** Thumbnail offset */\r
-       private static final int TAG_THUMBNAIL_OFFSET = 0x0201;\r
-       /** Thumbnail length */\r
-       private static final int TAG_THUMBNAIL_LENGTH = 0x0202;\r
-       /** Orientation of image */\r
-       private static final int TAG_ORIENTATION = 0x0112;\r
-       /** Bearing direction of image */\r
-       private static final int TAG_BEARING = 0x0011;\r
-\r
-\r
-       /**\r
-        * Creates an ExifReader for a Jpeg file\r
-        * @param inFile File object to attempt to read from\r
-        * @throws JpegException on failure\r
-        */\r
-       public ExifReader(File inFile) throws JpegException\r
-       {\r
-               _data = JpegSegmentReader.readExifSegment(inFile);\r
-       }\r
-\r
-       /**\r
-        * Performs the Exif data extraction\r
-        * @return the GPS data found in the file\r
-        */\r
-       public JpegData extract()\r
-       {\r
-               JpegData metadata = new JpegData();\r
-               if (_data==null)\r
-                       return metadata;\r
-\r
-               // check for the header length\r
-               if (_data.length<=14)\r
-               {\r
-                       metadata.addError("Exif data segment must contain at least 14 bytes");\r
-                       return metadata;\r
-               }\r
-\r
-               // check for the header preamble\r
-               if (!"Exif\0\0".equals(new String(_data, 0, 6)))\r
-               {\r
-                       metadata.addError("Exif data segment doesn't begin with 'Exif'");\r
-                       return metadata;\r
-               }\r
-\r
-               // this should be either "MM" or "II"\r
-               String byteOrderIdentifier = new String(_data, 6, 2);\r
-               if (!setByteOrder(byteOrderIdentifier))\r
-               {\r
-                       metadata.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);\r
-                       return metadata;\r
-               }\r
-\r
-               // Check the next two values are 0x2A as expected\r
-               if (get16Bits(8)!=0x2a)\r
-               {\r
-                       metadata.addError("Invalid Exif start - should have 0x2A at offset 8 in Exif header");\r
-                       return metadata;\r
-               }\r
-\r
-               int firstDirectoryOffset = get32Bits(10) + TIFF_HEADER_START_OFFSET;\r
-\r
-               // Check that offset is within range\r
-               if (firstDirectoryOffset>=_data.length - 1)\r
-               {\r
-                       metadata.addError("First exif directory offset is beyond end of Exif data segment");\r
-                       // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case\r
-                       firstDirectoryOffset = 14;\r
-               }\r
-\r
-               HashMap<Integer, String> processedDirectoryOffsets = new HashMap<Integer, String>();\r
-\r
-               // 0th IFD (we merge with Exif IFD)\r
-               processDirectory(metadata, false, processedDirectoryOffsets, firstDirectoryOffset, TIFF_HEADER_START_OFFSET);\r
-\r
-               return metadata;\r
-       }\r
-\r
-\r
-       /**\r
-        * Set the byte order identifier\r
-        * @param byteOrderIdentifier String from exif\r
-        * @return true if recognised, false otherwise\r
-        */\r
-       private boolean setByteOrder(String byteOrderIdentifier)\r
-       {\r
-               if ("MM".equals(byteOrderIdentifier)) {\r
-                       _isMotorolaByteOrder = true;\r
-               } else if ("II".equals(byteOrderIdentifier)) {\r
-                       _isMotorolaByteOrder = false;\r
-               } else {\r
-                       return false;\r
-               }\r
-               return true;\r
-       }\r
-\r
-\r
-       /**\r
-        * Recursive call to process one of the nested Tiff IFD directories.\r
-        * 2 bytes: number of tags\r
-        * for each tag\r
-        *   2 bytes: tag type\r
-        *   2 bytes: format code\r
-        *   4 bytes: component count\r
-        */\r
-       private void processDirectory(JpegData inMetadata, boolean inIsGPS, HashMap<Integer, String> inDirectoryOffsets,\r
-               int inDirOffset, int inTiffHeaderOffset)\r
-       {\r
-               // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist\r
-               if (inDirectoryOffsets.containsKey(Integer.valueOf(inDirOffset)))\r
-                       return;\r
-\r
-               // remember that we've visited this directory so that we don't visit it again later\r
-               inDirectoryOffsets.put(Integer.valueOf(inDirOffset), "processed");\r
-\r
-               if (inDirOffset >= _data.length || inDirOffset < 0)\r
-               {\r
-                       inMetadata.addError("Ignored directory marked to start outside data segment");\r
-                       return;\r
-               }\r
-\r
-               // First two bytes in the IFD are the number of tags in this directory\r
-               int dirTagCount = get16Bits(inDirOffset);\r
-               // If no tags, exit without complaint\r
-               if (dirTagCount == 0) return;\r
-\r
-               if (!isDirectoryLengthValid(inDirOffset, inTiffHeaderOffset))\r
-               {\r
-                       inMetadata.addError("Directory length is not valid");\r
-                       return;\r
-               }\r
-\r
-               inMetadata.setExifDataPresent();\r
-               // Handle each tag in this directory\r
-               for (int tagNumber = 0; tagNumber<dirTagCount; tagNumber++)\r
-               {\r
-                       final int tagOffset = calculateTagOffset(inDirOffset, tagNumber);\r
-\r
-                       // 2 bytes for the tag type\r
-                       final int tagType = get16Bits(tagOffset);\r
-\r
-                       // 2 bytes for the format code\r
-                       final int formatCode = get16Bits(tagOffset + 2);\r
-                       if (formatCode < 1 || formatCode > MAX_FORMAT_CODE)\r
-                       {\r
-                               inMetadata.addError("Invalid format code: " + formatCode);\r
-                               continue;\r
-                       }\r
-\r
-                       // 4 bytes dictate the number of components in this tag's data\r
-                       final int componentCount = get32Bits(tagOffset + 4);\r
-                       if (componentCount < 0)\r
-                       {\r
-                               inMetadata.addError("Negative component count in EXIF");\r
-                               continue;\r
-                       }\r
-                       // each component may have more than one byte... calculate the total number of bytes\r
-                       final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];\r
-                       final int tagValueOffset = calculateTagValueOffset(byteCount, tagOffset, inTiffHeaderOffset);\r
-                       if (tagValueOffset < 0 || tagValueOffset > _data.length)\r
-                       {\r
-                               inMetadata.addError("Illegal pointer offset value in EXIF");\r
-                               continue;\r
-                       }\r
-\r
-                       // Check that this tag isn't going to allocate outside the bounds of the data array.\r
-                       // This addresses an uncommon OutOfMemoryError.\r
-                       if (byteCount < 0 || tagValueOffset + byteCount > _data.length)\r
-                       {\r
-                               inMetadata.addError("Illegal number of bytes: " + byteCount);\r
-                               continue;\r
-                       }\r
-\r
-                       // Calculate the value as an offset for cases where the tag represents a directory\r
-                       final int subdirOffset = inTiffHeaderOffset + get32Bits(tagValueOffset);\r
-\r
-                       // Look in both basic Exif tags (for timestamp, thumbnail) and Gps tags (for lat, long, altitude, timestamp)\r
-                       switch (tagType)\r
-                       {\r
-                               case TAG_EXIF_OFFSET:\r
-                                       processDirectory(inMetadata, false, inDirectoryOffsets, subdirOffset, inTiffHeaderOffset);\r
-                                       continue;\r
-                               case TAG_INTEROP_OFFSET:\r
-                                       // ignore\r
-                                       continue;\r
-                               case TAG_GPS_INFO_OFFSET:\r
-                                       processDirectory(inMetadata, true, inDirectoryOffsets, subdirOffset, inTiffHeaderOffset);\r
-                                       continue;\r
-                               case TAG_MAKER_NOTE:\r
-                                       // ignore\r
-                                       continue;\r
-                               default:\r
-                                       // not a known directory, so must just be a normal tag\r
-                                       if (inIsGPS)\r
-                                       {\r
-                                               processGpsTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode);\r
-                                       }\r
-                                       else\r
-                                       {\r
-                                               processExifTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode);\r
-                                       }\r
-                                       break;\r
-                       }\r
-               }\r
-\r
-               // at the end of each IFD is an optional link to the next IFD\r
-               final int finalTagOffset = calculateTagOffset(inDirOffset, dirTagCount);\r
-               int nextDirectoryOffset = get32Bits(finalTagOffset);\r
-               if (nextDirectoryOffset != 0)\r
-               {\r
-                       nextDirectoryOffset += inTiffHeaderOffset;\r
-                       if (nextDirectoryOffset>=_data.length)\r
-                       {\r
-                               // Last 4 bytes of IFD reference another IFD with an address that is out of bounds\r
-                               return;\r
-                       }\r
-                       else if (nextDirectoryOffset < inDirOffset)\r
-                       {\r
-                               // Last 4 bytes of IFD reference another IFD with an address before the start of this directory\r
-                               return;\r
-                       }\r
-                       // the next directory is of same type as this one\r
-                       processDirectory(inMetadata, false, inDirectoryOffsets, nextDirectoryOffset, inTiffHeaderOffset);\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Check if the directory length is valid\r
-        * @param dirStartOffset start offset for directory\r
-        * @param tiffHeaderOffset Tiff header offeset\r
-        * @return true if length is valid\r
-        */\r
-       private boolean isDirectoryLengthValid(int inDirStartOffset, int inTiffHeaderOffset)\r
-       {\r
-               int dirTagCount = get16Bits(inDirStartOffset);\r
-               int dirLength = (2 + (12 * dirTagCount) + 4);\r
-               if (dirLength + inDirStartOffset + inTiffHeaderOffset >= _data.length)\r
-               {\r
-                       // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier might trigger this\r
-                       return false;\r
-               }\r
-               return true;\r
-       }\r
-\r
-\r
-       /**\r
-        * Process a GPS tag and put the contents in the given metadata\r
-        * @param inMetadata metadata holding extracted values\r
-        * @param inTagType tag type (eg latitude)\r
-        * @param inTagValueOffset start offset in data array\r
-        * @param inComponentCount component count for tag\r
-        * @param inFormatCode format code, eg byte\r
-        */\r
-       private void processGpsTag(JpegData inMetadata, int inTagType, int inTagValueOffset,\r
-               int inComponentCount, int inFormatCode)\r
-       {\r
-               try\r
-               {\r
-                       // Only interested in tags latref, lat, longref, lon, altref, alt and gps timestamp\r
-                       switch (inTagType)\r
-                       {\r
-                               case TAG_GPS_LATITUDE_REF:\r
-                                       inMetadata.setLatitudeRef(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
-                                       break;\r
-                               case TAG_GPS_LATITUDE:\r
-                                       Rational[] latitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       inMetadata.setLatitude(new double[] {latitudes[0].doubleValue(), latitudes[1].doubleValue(),\r
-                                               ExifGateway.convertToPositiveValue(latitudes[2].getNumerator(), latitudes[2].getDenominator())});\r
-                                       break;\r
-                               case TAG_GPS_LONGITUDE_REF:\r
-                                       inMetadata.setLongitudeRef(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
-                                       break;\r
-                               case TAG_GPS_LONGITUDE:\r
-                                       Rational[] longitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       inMetadata.setLongitude(new double[] {longitudes[0].doubleValue(), longitudes[1].doubleValue(),\r
-                                               ExifGateway.convertToPositiveValue(longitudes[2].getNumerator(), longitudes[2].getDenominator())});\r
-                                       break;\r
-                               case TAG_GPS_ALTITUDE_REF:\r
-                                       inMetadata.setAltitudeRef(_data[inTagValueOffset]);\r
-                                       break;\r
-                               case TAG_GPS_ALTITUDE:\r
-                                       inMetadata.setAltitude(readRational(inTagValueOffset, inFormatCode, inComponentCount).intValue());\r
-                                       break;\r
-                               case TAG_GPS_TIMESTAMP:\r
-                                       Rational[] times = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       inMetadata.setGpsTimestamp(new int[] {times[0].intValue(), times[1].intValue(), times[2].intValue()});\r
-                                       break;\r
-                               case TAG_GPS_DATESTAMP:\r
-                                       Rational[] dates = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       if (dates != null) {\r
-                                               inMetadata.setGpsDatestamp(new int[] {dates[0].intValue(), dates[1].intValue(), dates[2].intValue()});\r
-                                       }\r
-                                       else\r
-                                       {\r
-                                               // Not in rational array format, but maybe as String?\r
-                                               String date = readString(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                               if (date != null && date.length() == 10) {\r
-                                                       inMetadata.setGpsDatestamp(new int[] {Integer.parseInt(date.substring(0, 4)),\r
-                                                               Integer.parseInt(date.substring(5, 7)), Integer.parseInt(date.substring(8))});\r
-                                               }\r
-                                       }\r
-                                       break;\r
-                               case TAG_BEARING:\r
-                                       Rational val = readRational(inTagValueOffset, inFormatCode, inComponentCount);\r
-                                       if (val != null) {\r
-                                               inMetadata.setBearing(val.doubleValue());\r
-                                       }\r
-                                       break;\r
-                               default: // ignore all other tags\r
-                       }\r
-               }\r
-               catch (Exception e) {} // ignore and continue\r
-       }\r
-\r
-\r
-       /**\r
-        * Process a general Exif tag\r
-        * @param inMetadata metadata holding extracted values\r
-        * @param inTagType tag type (eg latitude)\r
-        * @param inTagValueOffset start offset in data array\r
-        * @param inComponentCount component count for tag\r
-        * @param inFormatCode format code, eg byte\r
-        */\r
-       private void processExifTag(JpegData inMetadata, int inTagType, int inTagValueOffset,\r
-               int inComponentCount, int inFormatCode)\r
-       {\r
-               // Only interested in original timestamp, thumbnail offset and thumbnail length\r
-               if (inTagType == TAG_DATETIME_ORIGINAL) {\r
-                       inMetadata.setOriginalTimestamp(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
-               }\r
-               else if (inTagType == TAG_DATETIME_DIGITIZED) {\r
-                       inMetadata.setDigitizedTimestamp(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
-               }\r
-               else if (inTagType == TAG_THUMBNAIL_OFFSET) {\r
-                       _thumbnailOffset = TIFF_HEADER_START_OFFSET + get16Bits(inTagValueOffset);\r
-                       extractThumbnail(inMetadata);\r
-               }\r
-               else if (inTagType == TAG_THUMBNAIL_LENGTH) {\r
-                       _thumbnailLength = get16Bits(inTagValueOffset);\r
-                       extractThumbnail(inMetadata);\r
-               }\r
-               else if (inTagType == TAG_ORIENTATION) {\r
-                       if (inMetadata.getOrientationCode() < 1) {\r
-                               inMetadata.setOrientationCode(get16Bits(inTagValueOffset));\r
-                       }\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Attempt to extract the thumbnail image\r
-        */\r
-       private void extractThumbnail(JpegData inMetadata)\r
-       {\r
-               if (_thumbnailOffset > 0 && _thumbnailLength > 0 && inMetadata.getThumbnailImage() == null)\r
-               {\r
-                       byte[] thumbnailBytes = new byte[_thumbnailLength];\r
-                       System.arraycopy(_data, _thumbnailOffset, thumbnailBytes, 0, _thumbnailLength);\r
-                       inMetadata.setThumbnailImage(thumbnailBytes);\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Calculate the tag value offset\r
-        * @param inByteCount\r
-        * @param inDirEntryOffset\r
-        * @param inTiffHeaderOffset\r
-        * @return new offset\r
-        */\r
-       private int calculateTagValueOffset(int inByteCount, int inDirEntryOffset, int inTiffHeaderOffset)\r
-       {\r
-               if (inByteCount > 4)\r
-               {\r
-                       // If it's bigger than 4 bytes, the dir entry contains an offset.\r
-                       // dirEntryOffset must be passed, as some makers (e.g. FujiFilm) incorrectly use an\r
-                       // offset relative to the start of the makernote itself, not the TIFF segment.\r
-                       final int offsetVal = get32Bits(inDirEntryOffset + 8);\r
-                       if (offsetVal + inByteCount > _data.length)\r
-                       {\r
-                               // Bogus pointer offset and / or bytecount value\r
-                               return -1; // signal error\r
-                       }\r
-                       return inTiffHeaderOffset + offsetVal;\r
-               }\r
-               else\r
-               {\r
-                       // 4 bytes or less and value is in the dir entry itself\r
-                       return inDirEntryOffset + 8;\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Creates a String from the _data buffer starting at the specified offset,\r
-        * and ending where byte=='\0' or where length==maxLength.\r
-        * @param inOffset start offset\r
-        * @param inFormatCode format code - should be string\r
-        * @param inMaxLength max length of string\r
-        * @return contents of tag, or null if format incorrect\r
-        */\r
-       private String readString(int inOffset, int inFormatCode, int inMaxLength)\r
-       {\r
-               if (inFormatCode != FMT_STRING) return null;\r
-               // Calculate length\r
-               int length = 0;\r
-               while ((inOffset + length)<_data.length\r
-                       && _data[inOffset + length]!='\0'\r
-                       && length < inMaxLength)\r
-               {\r
-                       length++;\r
-               }\r
-               return new String(_data, inOffset, length);\r
-       }\r
-\r
-       /**\r
-        * Creates a Rational from the _data buffer starting at the specified offset\r
-        * @param inOffset start offset\r
-        * @param inFormatCode format code - should be srational or urational\r
-        * @param inCount component count - should be 1\r
-        * @return contents of tag as a Rational object\r
-        */\r
-       private Rational readRational(int inOffset, int inFormatCode, int inCount)\r
-       {\r
-               // Check the format is a single rational as expected\r
-               if (inFormatCode != FMT_SRATIONAL && inFormatCode != FMT_URATIONAL\r
-                       || inCount != 1) return null;\r
-               return new Rational(get32Bits(inOffset), get32Bits(inOffset + 4));\r
-       }\r
-\r
-\r
-       /**\r
-        * Creates a Rational array from the _data buffer starting at the specified offset\r
-        * @param inOffset start offset\r
-        * @param inFormatCode format code - should be srational or urational\r
-        * @param inCount component count - number of components\r
-        * @return contents of tag as an array of Rational objects\r
-        */\r
-       private Rational[] readRationalArray(int inOffset, int inFormatCode, int inCount)\r
-       {\r
-               // Check the format is rational as expected\r
-               if (inFormatCode != FMT_SRATIONAL && inFormatCode != FMT_URATIONAL)\r
-                       return null;\r
-               // Build array of Rationals\r
-               Rational[] answer = new Rational[inCount];\r
-               for (int i=0; i<inCount; i++)\r
-                       answer[i] = new Rational(get32Bits(inOffset + (8 * i)), get32Bits(inOffset + 4 + (8 * i)));\r
-               return answer;\r
-       }\r
-\r
-\r
-       /**\r
-        * Determine the offset at which a given InteropArray entry begins within the specified IFD.\r
-        * @param dirStartOffset the offset at which the IFD starts\r
-        * @param entryNumber the zero-based entry number\r
-        */\r
-       private int calculateTagOffset(int dirStartOffset, int entryNumber)\r
-       {\r
-               // add 2 bytes for the tag count\r
-               // each entry is 12 bytes, so we skip 12 * the number seen so far\r
-               return dirStartOffset + 2 + (12 * entryNumber);\r
-       }\r
-\r
-\r
-       /**\r
-        * Get a 16 bit value from file's native byte order.  Between 0x0000 and 0xFFFF.\r
-        */\r
-       private int get16Bits(int offset)\r
-       {\r
-               if (offset<0 || offset+2>_data.length)\r
-                       throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index "\r
-                               + offset + " where max index is " + (_data.length - 1) + ")");\r
-\r
-               if (_isMotorolaByteOrder) {\r
-                       // Motorola - MSB first\r
-                       return (_data[offset] << 8 & 0xFF00) | (_data[offset + 1] & 0xFF);\r
-               } else {\r
-                       // Intel ordering - LSB first\r
-                       return (_data[offset + 1] << 8 & 0xFF00) | (_data[offset] & 0xFF);\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Get a 32 bit value from file's native byte order.\r
-        */\r
-       private int get32Bits(int offset)\r
-       {\r
-               if (offset < 0 || offset+4 > _data.length)\r
-                       throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index "\r
-                               + offset + " where max index is " + (_data.length - 1) + ")");\r
-\r
-               if (_isMotorolaByteOrder)\r
-               {\r
-                       // Motorola - MSB first\r
-                       return (_data[offset] << 24 & 0xFF000000) |\r
-                                       (_data[offset + 1] << 16 & 0xFF0000) |\r
-                                       (_data[offset + 2] << 8 & 0xFF00) |\r
-                                       (_data[offset + 3] & 0xFF);\r
-               }\r
-               else\r
-               {\r
-                       // Intel ordering - LSB first\r
-                       return (_data[offset + 3] << 24 & 0xFF000000) |\r
-                                       (_data[offset + 2] << 16 & 0xFF0000) |\r
-                                       (_data[offset + 1] << 8 & 0xFF00) |\r
-                                       (_data[offset] & 0xFF);\r
-               }\r
-       }\r
-}\r
+package tim.prune.jpeg.drew;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import tim.prune.jpeg.JpegData;
+
+
+/**
+ * Extracts Exif data from a JPEG header segment
+ * Based on Drew Noakes' Metadata extractor at https://drewnoakes.com
+ * which in turn is based on code from Jhead http://www.sentex.net/~mwandel/jhead/
+ */
+public class ExifReader
+{
+       /** Magic numbers to mark the beginning of all Jpegs */
+       private static final int MAGIC_JPEG_BYTE_1 = 0xFF;
+       private static final int MAGIC_JPEG_BYTE_2 = 0xD8;
+
+       /** 6-byte preamble before starting the TIFF data. */
+       private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
+
+       /** Start of segment marker */
+       private static final byte SEGMENT_SOS = (byte) 0xDA;
+
+       /** End of segment marker */
+       private static final byte MARKER_EOI = (byte) 0xD9;
+
+       /**
+        * Processes the provided JPEG data, and extracts the specified JPEG segments into a JpegData object.
+        * @param inFile a {@link File} from which the JPEG data will be read.
+        */
+       public static JpegData readMetadata(File inFile) throws ExifException
+       {
+               JpegData jpegData = new JpegData();
+               BufferedInputStream bStream = null;
+
+               try
+               {
+                       bStream = new BufferedInputStream(new FileInputStream(inFile));
+                       byte[] segmentBytes = readSegments(bStream);
+                       if (segmentBytes != null)
+                       {
+                               // Got the bytes for the required segment, now extract the data
+                               extract(segmentBytes, jpegData);
+                       }
+               }
+               catch (IOException ioe) {
+                       throw new ExifException("IO Exception: " + ioe.getMessage());
+               }
+               finally
+               {
+                       if (bStream != null) {
+                               try {
+                                       bStream.close();
+                               } catch (IOException ioe) {}
+                       }
+               }
+               return jpegData;
+       }
+
+       /**
+        * Reads the relevant segment and returns the bytes.
+        */
+       private static byte[] readSegments(final BufferedInputStream bStream)
+               throws ExifException, IOException
+       {
+               // first two bytes should be JPEG magic number
+               final int magic1 = bStream.read() & 0xFF;
+               final int magic2 = bStream.read() & 0xFF;
+               if (magic1 != MAGIC_JPEG_BYTE_1 || magic2 != MAGIC_JPEG_BYTE_2) {
+                       throw new ExifException("Jpeg file failed Magic check");
+               }
+
+               final Byte segmentTypeByte = (byte)0xE1; // JpegSegmentType.APP1.byteValue;
+
+               do {
+                       // Find the segment marker. Markers are zero or more 0xFF bytes, followed
+                       // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
+
+                       final short segmentIdentifier = (short) bStream.read();
+
+                       // We must have at least one 0xFF byte
+                       if (segmentIdentifier != 0xFF)
+                               throw new ExifException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase());
+
+                       // Read until we have a non-0xFF byte. This identifies the segment type.
+                       byte currSegmentType = (byte) bStream.read();
+                       while (currSegmentType == (byte)0xFF) {
+                               currSegmentType = (byte) bStream.read();
+                       }
+
+                       if (currSegmentType == 0)
+                               throw new ExifException("Expected non-zero byte as part of JPEG marker identifier");
+
+                       if (currSegmentType == SEGMENT_SOS) {
+                               // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
+                               // have to search for the two bytes: 0xFF 0xD9 (EOI).
+                               // It comes last so simply return at this point
+                               return null;
+                       }
+
+                       if (currSegmentType == MARKER_EOI) {
+                               // the 'End-Of-Image' segment -- this should never be found in this fashion
+                               return null;
+                       }
+
+                       // next 2-bytes are <segment-size>: [high-byte] [low-byte]
+                       int segmentLength = (bStream.read() << 8) + bStream.read();
+                       // segment length includes size bytes, so subtract two
+                       segmentLength -= 2;
+
+                       if (segmentLength < 0)
+                               throw new ExifException("JPEG segment size would be less than zero");
+
+                       byte[] segmentBytes = new byte[segmentLength];
+                       int bytesRead = bStream.read(segmentBytes, 0, segmentLength);
+                       // Bail if not all bytes read in one go - otherwise following sections will be out of step
+                       if (bytesRead != segmentLength) {
+                               throw new ExifException("Tried to read " + segmentLength + " bytes but only got " + bytesRead);
+                       }
+                       // Check whether we are interested in this segment
+                       if (segmentTypeByte == currSegmentType)
+                       {
+                               // Pass the appropriate byte arrays to reader.
+                               if (canProcess(segmentBytes)) {
+                                       return segmentBytes;
+                               }
+                       }
+
+               } while (true);
+       }
+
+       private static boolean canProcess(final byte[] segmentBytes)
+       {
+               return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE);
+       }
+
+       /**
+        * Given the bytes, parse them recursively to fill the JpegData
+        * @param segmentBytes bytes out of the file
+        * @param jdata jpeg data to be populated
+        */
+       private static void extract(final byte[] segmentBytes, final JpegData jdata)
+       {
+               if (segmentBytes == null)
+                       throw new NullPointerException("segmentBytes cannot be null");
+
+               try
+               {
+                       ByteArrayReader reader = new ByteArrayReader(segmentBytes);
+
+                       // Check for the header preamble
+                       try {
+                               if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) {
+                                       // TODO what to do with this error state?
+                                       System.err.println("Invalid JPEG Exif segment preamble");
+                                       return;
+                               }
+                       } catch (ExifException e) {
+                               // TODO what to do with this error state?
+                               e.printStackTrace(System.err);
+                               return;
+                       }
+
+                       // Read the TIFF-formatted Exif data
+                       TiffProcessor.processTiff(
+                               reader,
+                               jdata,
+                               JPEG_EXIF_SEGMENT_PREAMBLE.length()
+                       );
+
+               } catch (ExifException e) {
+                       // TODO what to do with this error state?
+                       e.printStackTrace(System.err);
+               } catch (IOException e) {
+                       // TODO what to do with this error state?
+                       e.printStackTrace(System.err);
+               }
+       }
+}
diff --git a/tim/prune/jpeg/drew/ExifTiffHandler.java b/tim/prune/jpeg/drew/ExifTiffHandler.java
new file mode 100644 (file)
index 0000000..a98e720
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package tim.prune.jpeg.drew;
+
+import tim.prune.jpeg.JpegData;
+
+/**
+ * Implementation of TiffHandler used for handling TIFF tags according to the Exif standard.
+ *
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class ExifTiffHandler
+{
+       private JpegData _jpegData = null;
+       private long _thumbnailOffset = -1L, _thumbnailLength = -1L;
+
+       /** This tag is a pointer to the Exif SubIFD. */
+       final int DIR_EXIF_SUB_IFD_OFFSET = 0x8769;
+       /** This tag is a pointer to the Exif GPS IFD. */
+       final int DIR_GPS_INFO_OFFSET = 0x8825;
+
+       private static final int TAG_GPS_LATITUDE_REF  = 0x0001;
+       private static final int TAG_GPS_LATITUDE      = 0x0002;
+       private static final int TAG_GPS_LONGITUDE_REF = 0x0003;
+       private static final int TAG_GPS_LONGITUDE     = 0x0004;
+       private static final int TAG_GPS_ALTITUDE      = 0x0006;
+       private static final int TAG_GPS_BEARING       = 0x0011;
+
+       private static final int TAG_ORIENTATION       = 0x0112;
+       private static final int TAG_THUMBNAIL_OFFSET  = 0x0201;
+       private static final int TAG_THUMBNAIL_LENGTH  = 0x0202;
+
+       private static final int TAG_SUB_ORITIME       = 0x9003;
+       private static final int TAG_SUB_DIGITIME      = 0x9004;
+
+
+       /**
+        * Constructor
+        * @param jpegData data object to populate with received results
+        */
+       public ExifTiffHandler(JpegData jpegData)
+       {
+               _jpegData = jpegData;
+               _thumbnailOffset = _thumbnailLength = -1L;
+       }
+
+       public boolean isTagIfdPointer(int tagType)
+       {
+               if (tagType == DIR_EXIF_SUB_IFD_OFFSET) {
+                       return true;
+               } else if (tagType == DIR_GPS_INFO_OFFSET) {
+                       return true;
+               }
+
+               return false;
+       }
+
+       public void completed(final ByteArrayReader reader, final int tiffHeaderOffset)
+       {
+               // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
+               if (_thumbnailOffset >= 0L && _thumbnailLength > 0L)
+               {
+                       try {
+                               byte[] thumbData = reader.getBytes(tiffHeaderOffset + (int) _thumbnailOffset, (int) _thumbnailLength);
+                               if (thumbData != null)
+                               {
+                                       byte[] thumbCopy = new byte[thumbData.length];
+                                       System.arraycopy(thumbData, 0, thumbCopy, 0, thumbData.length);
+                                       _jpegData.setThumbnailImage(thumbCopy);
+                               }
+                       } catch (ExifException ex) {}
+               }
+       }
+
+       public void setRationalArray(int tagId, Rational[] array)
+       {
+               switch (tagId)
+               {
+                       case TAG_GPS_LATITUDE:
+                               _jpegData.setLatitude(new double[] {array[0].doubleValue(), array[1].doubleValue(),
+                                       array[2].convertToPositiveValue()});
+                               break;
+                       case TAG_GPS_LONGITUDE:
+                               _jpegData.setLongitude(new double[] {array[0].doubleValue(), array[1].doubleValue(),
+                                       array[2].convertToPositiveValue()});
+                               break;
+               }
+       }
+
+       public void setRational(int tagId, Rational rational)
+       {
+               switch (tagId)
+               {
+                       case TAG_GPS_ALTITUDE:
+                               _jpegData.setAltitude(rational.intValue());
+                               return;
+                       case TAG_GPS_BEARING:
+                               _jpegData.setBearing(rational.doubleValue());
+                               return;
+               }
+               // maybe it was an integer passed as a rational?
+               if (rational.getDenominator() == 1L) {
+                       setIntegerValue(tagId, rational.intValue());
+               }
+       }
+
+       public void setString(int tagId, String string)
+       {
+               switch (tagId)
+               {
+                       case TAG_SUB_ORITIME:
+                               _jpegData.setOriginalTimestamp(string);
+                               break;
+                       case TAG_SUB_DIGITIME:
+                               _jpegData.setDigitizedTimestamp(string);
+                               break;
+                       case TAG_GPS_LATITUDE_REF:
+                               _jpegData.setLatitudeRef(string);
+                               break;
+                       case TAG_GPS_LONGITUDE_REF:
+                               _jpegData.setLongitudeRef(string);
+                               break;
+               }
+       }
+
+       public void setIntegerValue(int tagId, int intVal)
+       {
+               switch (tagId)
+               {
+                       case TAG_ORIENTATION:
+                               _jpegData.setOrientationCode(intVal);
+                               break;
+                       case TAG_THUMBNAIL_OFFSET:
+                               _thumbnailOffset = intVal;
+                               break;
+                       case TAG_THUMBNAIL_LENGTH:
+                               _thumbnailLength = intVal;
+                               break;
+               }
+       }
+
+
+       /**
+        * Decide, based on the directory id and the tag id, if we want to parse and process it
+        * @param inDirectoryId
+        * @param childTagId
+        * @return true if the tag should be parsed
+        */
+       public boolean isInterestingTag(int inDirectoryId, int childTagId)
+       {
+               switch (inDirectoryId)
+               {
+                       case DIR_GPS_INFO_OFFSET:
+                               return true;
+                       case DIR_EXIF_SUB_IFD_OFFSET:
+                               return childTagId == TAG_SUB_ORITIME
+                                       || childTagId == TAG_SUB_DIGITIME;
+                       case 0:
+                               return childTagId == TAG_THUMBNAIL_OFFSET
+                                       || childTagId == TAG_THUMBNAIL_LENGTH
+                                       || childTagId == TAG_ORIENTATION;
+               }
+               return false;
+       }
+}
diff --git a/tim/prune/jpeg/drew/JpegException.java b/tim/prune/jpeg/drew/JpegException.java
deleted file mode 100644 (file)
index d85a3b7..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package tim.prune.jpeg.drew;
-
-/**
- * Class to indicate a fatal exception processing a jpeg,
- * including IO errors and exif errors
- */
-public class JpegException extends Exception
-{
-       /**
-        * @param message description of error
-        */
-       public JpegException(String message)
-       {
-               super(message);
-       }
-
-       /**
-        * @param message description of error
-        * @param cause Throwable which caused the error
-        */
-       public JpegException(String message, Throwable cause)
-       {
-               super(message, cause);
-       }
-}
diff --git a/tim/prune/jpeg/drew/JpegSegmentData.java b/tim/prune/jpeg/drew/JpegSegmentData.java
deleted file mode 100644 (file)
index 4b56cbe..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package tim.prune.jpeg.drew;\r
-\r
-import java.util.ArrayList;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-\r
-/**\r
- * Class to hold a collection of Jpeg data segments\r
- * Each marker represents a list of multiple byte arrays\r
- * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com\r
- */\r
-public class JpegSegmentData\r
-{\r
-       /** A map of byte[], keyed by the segment marker */\r
-       private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10);\r
-\r
-\r
-       /**\r
-        * Add a segment to the collection\r
-        * @param inSegmentMarker marker byte\r
-        * @param inSegmentBytes data of segment\r
-        */\r
-       public void addSegment(byte inSegmentMarker, byte[] inSegmentBytes)\r
-       {\r
-               List<byte[]> segmentList = getOrCreateSegmentList(inSegmentMarker);\r
-               segmentList.add(inSegmentBytes);\r
-       }\r
-\r
-\r
-       /**\r
-        * Get the first segment with the given marker\r
-        * @param inSegmentMarker marker byte\r
-        * @return first segment with that marker\r
-        */\r
-       public byte[] getSegment(byte inSegmentMarker)\r
-       {\r
-               return getSegment(inSegmentMarker, 0);\r
-       }\r
-\r
-\r
-       /**\r
-        * Get the nth segment with the given marker\r
-        * @param inSegmentMarker marker byte\r
-        * @param inOccurrence occurrence to get, starting at 0\r
-        * @return byte array from specified segment\r
-        */\r
-       public byte[] getSegment(byte inSegmentMarker, int inOccurrence)\r
-       {\r
-               final List<byte[]> segmentList = getSegmentList(inSegmentMarker);\r
-\r
-               if (segmentList==null || segmentList.size()<=inOccurrence)\r
-                       return null;\r
-               else\r
-                       return segmentList.get(inOccurrence);\r
-       }\r
-\r
-\r
-       /**\r
-        * Get the number of segments with the given marker\r
-        * @param inSegmentMarker marker byte\r
-        * @return number of segments\r
-        */\r
-       public int getSegmentCount(byte inSegmentMarker)\r
-       {\r
-               final List<byte[]> segmentList = getSegmentList(inSegmentMarker);\r
-               if (segmentList == null)\r
-                       return 0;\r
-               else\r
-                       return segmentList.size();\r
-       }\r
-\r
-\r
-       /**\r
-        * Get the list of segments with the given marker\r
-        * @param inSegmentMarker marker byte\r
-        * @return list of segments\r
-        */\r
-       private List<byte[]> getSegmentList(byte inSegmentMarker)\r
-       {\r
-               return _segmentDataMap.get(Byte.valueOf(inSegmentMarker));\r
-       }\r
-\r
-\r
-       /**\r
-        * Get the specified segment if it exists, otherwise create new one\r
-        * @param inSegmentMarker marker byte\r
-        * @return list of segments\r
-        */\r
-       private List<byte[]> getOrCreateSegmentList(byte inSegmentMarker)\r
-       {\r
-               List<byte[]> segmentList = null;\r
-               Byte key = Byte.valueOf(inSegmentMarker);\r
-               if (_segmentDataMap.containsKey(key))\r
-               {\r
-                       // list already exists\r
-                       segmentList = _segmentDataMap.get(key);\r
-               }\r
-               else\r
-               {\r
-                       // create new list and add it\r
-                       segmentList = new ArrayList<byte[]>();\r
-                       _segmentDataMap.put(key, segmentList);\r
-               }\r
-               return segmentList;\r
-       }\r
-}\r
diff --git a/tim/prune/jpeg/drew/JpegSegmentReader.java b/tim/prune/jpeg/drew/JpegSegmentReader.java
deleted file mode 100644 (file)
index 65515a1..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package tim.prune.jpeg.drew;\r
-\r
-import java.io.*;\r
-\r
-/**\r
- * Class to perform read functions of Jpeg files, returning specific file segments\r
- * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com\r
- */\r
-public class JpegSegmentReader\r
-{\r
-       /** Start of scan marker */\r
-       private static final byte SEGMENT_SOS = (byte)0xDA;\r
-\r
-       /** End of image marker */\r
-       private static final byte MARKER_EOI = (byte)0xD9;\r
-\r
-       /** APP1 Jpeg segment identifier -- where Exif data is kept. */\r
-       private static final byte SEGMENT_APP1 = (byte)0xE1;\r
-\r
-       /** Magic numbers to mark the beginning of all Jpegs */\r
-       private static final int MAGIC_JPEG_BYTE_1 = 0xFF;\r
-       private static final int MAGIC_JPEG_BYTE_2 = 0xD8;\r
-\r
-\r
-       /**\r
-        * Get the Exif data segment for the specified file\r
-        * @param inFile File to read\r
-        * @return Exif data segment as byte array\r
-        * @throws JpegException on file read errors or exif data errors\r
-        */\r
-       public static byte[] readExifSegment(File inFile) throws JpegException\r
-       {\r
-               JpegSegmentData data = readSegments(inFile);\r
-               return data.getSegment(SEGMENT_APP1);\r
-       }\r
-\r
-\r
-       /**\r
-        * Obtain the Jpeg segment data from the specified file\r
-        * @param inFile File to read\r
-        * @return Jpeg segment data from file\r
-        * @throws JpegException on file read errors or exif data errors\r
-        */\r
-       private static JpegSegmentData readSegments(File inFile) throws JpegException\r
-       {\r
-               JpegSegmentData segmentData = new JpegSegmentData();\r
-               BufferedInputStream bStream = null;\r
-\r
-               try\r
-               {\r
-                       bStream = new BufferedInputStream(new FileInputStream(inFile));\r
-                       // first two bytes should be jpeg magic number\r
-                       final int magic1 = bStream.read() & 0xFF;\r
-                       final int magic2 = bStream.read() & 0xFF;\r
-                       if (magic1 != MAGIC_JPEG_BYTE_1 || magic2 != MAGIC_JPEG_BYTE_2) {\r
-                               throw new JpegException("not a jpeg file");\r
-                       }\r
-\r
-                       // Loop around segments found\r
-                       boolean foundExif = false;\r
-                       do\r
-                       {\r
-                               // next byte is 0xFF\r
-                               byte segmentIdentifier = (byte) (bStream.read() & 0xFF);\r
-                               if ((segmentIdentifier & 0xFF) != 0xFF)\r
-                               {\r
-                                       throw new JpegException("expected jpeg segment start 0xFF, not 0x"\r
-                                               + Integer.toHexString(segmentIdentifier & 0xFF));\r
-                               }\r
-                               // next byte is <segment-marker>\r
-                               byte thisSegmentMarker = (byte) (bStream.read() & 0xFF);\r
-                               // next 2-bytes are <segment-size>: [high-byte] [low-byte]\r
-                               byte[] segmentLengthBytes = new byte[2];\r
-                               bStream.read(segmentLengthBytes, 0, 2);\r
-                               int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);\r
-                               // segment length includes size bytes, so subtract two\r
-                               segmentLength -= 2;\r
-                               if (segmentLength > bStream.available())\r
-                                       throw new JpegException("segment size would extend beyond file stream length");\r
-                               else if (segmentLength < 0)\r
-                                       throw new JpegException("segment size would be less than zero");\r
-                               byte[] segmentBytes = new byte[segmentLength];\r
-                               int bytesRead = bStream.read(segmentBytes, 0, segmentLength);\r
-                               // Bail if not all bytes read in one go - otherwise following sections will be out of step\r
-                               if (bytesRead != segmentLength) {\r
-                                       throw new JpegException("Tried to read " + segmentLength + " bytes but only got " + bytesRead);\r
-                               }\r
-                               if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF))\r
-                               {\r
-                                       // The 'Start-Of-Scan' segment comes last so break out of loop\r
-                                       break;\r
-                               }\r
-                               else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF))\r
-                               {\r
-                                       // the 'End-Of-Image' segment - should already have exited by now\r
-                                       break;\r
-                               }\r
-                               else\r
-                               {\r
-                                       segmentData.addSegment(thisSegmentMarker, segmentBytes);\r
-                               }\r
-                               // loop through to the next segment if exif hasn't already been found\r
-                               foundExif = (thisSegmentMarker == SEGMENT_APP1);\r
-                       }\r
-                       while (!foundExif);\r
-               }\r
-               catch (FileNotFoundException fnfe)\r
-               {\r
-                       throw new JpegException("Jpeg file not found");\r
-               }\r
-               catch (IOException ioe)\r
-               {\r
-                       throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);\r
-               }\r
-               finally\r
-               {\r
-                       try\r
-                       {\r
-                               if (bStream != null) {\r
-                                       bStream.close();\r
-                               }\r
-                       }\r
-                       catch (IOException ioe) {\r
-                               throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);\r
-                       }\r
-               }\r
-               // Return the result\r
-               return segmentData;\r
-       }\r
-}\r
index 3c83b9f5d88595fadb140d4dbb4a82532d6cf495..8395898266677c16593604c8a04a0826db490225 100644 (file)
-package tim.prune.jpeg.drew;\r
-\r
-/**\r
- * Immutable class for holding a rational number without loss of precision.\r
- * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com\r
- */\r
-public class Rational\r
-{\r
-       /** Holds the numerator */\r
-       private final int _numerator;\r
-\r
-       /** Holds the denominator */\r
-       private final int _denominator;\r
-\r
-       /**\r
-        * Constructor\r
-        * @param inNumerator numerator of fraction (upper number)\r
-        * @param inDenominator denominator of fraction (lower number)\r
-        */\r
-       public Rational(int inNumerator, int inDenominator)\r
-       {\r
-               // Could throw exception if denominator is zero\r
-               _numerator = inNumerator;\r
-               _denominator = inDenominator;\r
-       }\r
-\r
-\r
-       /**\r
-        * @return the value of the specified number as a <code>double</code>.\r
-        * This may involve rounding.\r
-        */\r
-       public double doubleValue()\r
-       {\r
-               if (_denominator == 0) return 0.0;\r
-               return (double)_numerator / (double)_denominator;\r
-       }\r
-\r
-       /**\r
-        * @return the value of the specified number as an <code>int</code>.\r
-        * This may involve rounding or truncation.\r
-        */\r
-       public final int intValue()\r
-       {\r
-               if (_denominator == 0) return 0;\r
-               return _numerator / _denominator;\r
-       }\r
-\r
-       /**\r
-        * @return the denominator.\r
-        */\r
-       public final int getDenominator()\r
-       {\r
-               return _denominator;\r
-       }\r
-\r
-       /**\r
-        * @return the numerator.\r
-        */\r
-       public final int getNumerator()\r
-       {\r
-               return _numerator;\r
-       }\r
-\r
-       /**\r
-        * Checks if this rational number is an Integer, either positive or negative\r
-        * @return true if an integer\r
-        */\r
-       public boolean isInteger()\r
-       {\r
-               // number is integer if the denominator is 1, or if the remainder is zero\r
-               return (_denominator == 1\r
-                       || (_denominator != 0 && (_numerator % _denominator == 0)));\r
-       }\r
-\r
-\r
-       /**\r
-        * @return a string representation of the object of form <code>numerator/denominator</code>.\r
-        */\r
-       public String toString()\r
-       {\r
-               return "" + _numerator + "/" + _denominator;\r
-       }\r
-\r
-\r
-       /**\r
-        * Compares two <code>Rational</code> instances, returning true if they are equal\r
-        * @param inOther the Rational to compare this instance to.\r
-        * @return true if instances are equal, otherwise false.\r
-        */\r
-       public boolean equals(Rational inOther)\r
-       {\r
-               // Could also attempt to simplify fractions to lowest common denominator before compare\r
-               return _numerator == inOther._numerator && _denominator == inOther._denominator;\r
-       }\r
-}
\ No newline at end of file
+package tim.prune.jpeg.drew;
+
+/**
+ * Immutable class for holding a rational number without loss of precision.
+ * Based on Drew Noakes' Metadata extractor at https://drewnoakes.com
+ */
+public class Rational
+{
+       /** Holds the numerator */
+       private final long _numerator;
+
+       /** Holds the denominator */
+       private final long _denominator;
+
+       /**
+        * Creates a new (immutable) instance of Rational.
+        */
+       public Rational(long numerator, long denominator)
+       {
+               _numerator = numerator;
+               _denominator = denominator;
+       }
+
+       /**
+        * @return the value of the specified number as a <code>double</code>.
+        * This may involve rounding.
+        */
+       public double doubleValue()
+       {
+               if (_denominator == 0L) return 0.0;
+               return (double)_numerator / (double)_denominator;
+       }
+
+       /**
+        * Returns the value of the specified number as an <code>int</code>.
+        */
+       public final int intValue()
+       {
+               return (int) longValue();
+       }
+
+       /**
+        * Returns the value of the specified number as a <code>long</code>.
+        * This may involve rounding or truncation.
+        * If the denominator is 0, returns 0 to avoid dividing by 0.
+        */
+       public final long longValue()
+       {
+               if (_denominator == 0L) return 0L;
+               return _numerator / _denominator;
+       }
+
+       /** Returns the denominator */
+       public final long getDenominator()
+       {
+               return _denominator;
+       }
+
+       /** Returns the numerator */
+       public final long getNumerator()
+       {
+               return _numerator;
+       }
+
+       /**
+        * @return the value of the specified number as a positive <code>double</code>.
+        * Prevents interpretation of 32 bit numbers as negative, and forces a positive answer.
+        * Needed?
+        */
+       public double convertToPositiveValue()
+       {
+               if (_denominator == 0L) return 0.0;
+               double numeratorDbl = _numerator;
+               double denomDbl = _denominator;
+               if (_numerator >= 0L) {
+                       // Numerator is positive (but maybe denominator isn't?)
+                       return numeratorDbl / denomDbl;
+               }
+               // Add 2^32 to negative doubles to make them positive
+               final double correction = Math.pow(2.0, 32);
+               numeratorDbl += correction;
+               if (_denominator < 0L) {
+                       denomDbl += correction;
+               }
+               return numeratorDbl / denomDbl;
+       }
+
+       /**
+        * Returns a string representation of the object of form <code>numerator/denominator</code>.
+        */
+       @Override
+       public String toString()
+       {
+               return "" + _numerator + "/" + _denominator;
+       }
+
+       /**
+        * Compares two {@link Rational} instances, returning true if they are mathematically
+        * equivalent.
+        *
+        * @param obj the {@link Rational} to compare this instance to.
+        * @return true if instances are mathematically equivalent, otherwise false.  Will also
+        *         give false if <code>obj</code> is not an instance of {@link Rational}.
+        */
+       @Override
+       public boolean equals( Object obj)
+       {
+               if (obj==null || !(obj instanceof Rational))
+                       return false;
+               Rational that = (Rational) obj;
+               return this.doubleValue() == that.doubleValue();
+       }
+}
diff --git a/tim/prune/jpeg/drew/TiffDataFormat.java b/tim/prune/jpeg/drew/TiffDataFormat.java
new file mode 100644 (file)
index 0000000..6c89fc3
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package tim.prune.jpeg.drew;
+
+/**
+ * An enumeration of data formats used by the TIFF specification.
+ *
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class TiffDataFormat
+{
+       public static final int CODE_INT8_U = 1;
+       public static final int CODE_STRING = 2;
+       public static final int CODE_INT16_U = 3;
+       public static final int CODE_INT32_U = 4;
+       public static final int CODE_RATIONAL_U = 5;
+       public static final int CODE_INT8_S = 6;
+       public static final int CODE_UNDEFINED = 7;
+       public static final int CODE_INT16_S = 8;
+       public static final int CODE_INT32_S = 9;
+       public static final int CODE_RATIONAL_S = 10;
+       public static final int CODE_SINGLE = 11;
+       public static final int CODE_DOUBLE = 12;
+
+       public static int getComponentSize(int tiffFormatCode)
+       {
+               switch (tiffFormatCode)
+               {
+                       case CODE_INT8_U:
+                       case CODE_STRING:
+                       case CODE_INT8_S:
+                       case CODE_UNDEFINED:
+                               return 1;
+                       case CODE_INT16_U:
+                       case CODE_INT16_S:
+                       case CODE_INT32_U:
+                       case CODE_INT32_S:
+                       case CODE_SINGLE:
+                               return 4;
+                       case CODE_RATIONAL_U:
+                       case CODE_RATIONAL_S:
+                       case CODE_DOUBLE:
+                               return 8;
+                       default:
+                               return 0;
+               }
+       }
+}
diff --git a/tim/prune/jpeg/drew/TiffProcessor.java b/tim/prune/jpeg/drew/TiffProcessor.java
new file mode 100644 (file)
index 0000000..2811a7f
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package tim.prune.jpeg.drew;
+
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import tim.prune.jpeg.JpegData;
+
+/**
+ * Processes TIFF-formatted data, using an ExifTiffHandler
+ *
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class TiffProcessor
+{
+       /**
+        * Processes a TIFF data sequence.
+        *
+        * @param reader the {@link RandomAccessReader} from which the data should be read
+        * @param jpegData the data to populate
+        * @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
+        * @throws ExifException if an error occurred during the processing of TIFF data that could
+        *                       not be ignored or recovered from
+        * @throws IOException an error occurred while accessing the required data
+        */
+       public static void processTiff(final ByteArrayReader reader, JpegData jpegData,
+               final int tiffHeaderOffset) throws ExifException, IOException
+       {
+               // This must be either "MM" or "II".
+               short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset);
+
+               if (byteOrderIdentifier == 0x4d4d) { // "MM"
+                       reader.setMotorolaByteOrder(true);
+               } else if (byteOrderIdentifier == 0x4949) { // "II"
+                       reader.setMotorolaByteOrder(false);
+               } else {
+                       throw new ExifException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
+               }
+
+
+               int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
+
+               // David Ekholm sent a digital camera image that has this problem
+               if (firstIfdOffset >= reader.getLength() - 1) {
+                       //handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset");
+                       // First directory normally starts immediately after the offset bytes, so try that
+                       firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4;
+               }
+
+               // Make a handler object to use for the processing
+               ExifTiffHandler handler = new ExifTiffHandler(jpegData);
+
+               Set<Integer> processedIfdOffsets = new HashSet<Integer>();
+               processDirectory(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset, 0);
+
+               handler.completed(reader, tiffHeaderOffset);
+       }
+
+       /**
+        * Processes a TIFF IFD.
+        *
+        * IFD Header:
+        * <ul>
+        *     <li><b>2 bytes</b> number of tags</li>
+        * </ul>
+        * Tag structure:
+        * <ul>
+        *     <li><b>2 bytes</b> tag type</li>
+        *     <li><b>2 bytes</b> format code (values 1 to 12, inclusive)</li>
+        *     <li><b>4 bytes</b> component count</li>
+        *     <li><b>4 bytes</b> inline value, or offset pointer if too large to fit in four bytes</li>
+        * </ul>
+        *
+        * @param handler the {@link ExifTiffHandler} that will coordinate processing and accept read values
+        * @param reader the byte reader from which the data should be read
+        * @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop
+        * @param ifdOffset the offset within <code>reader</code> at which the IFD data starts
+        * @param tiffHeaderOffset the offset within <code>reader</code> at which the TIFF header starts
+        * @param inDirectoryId directory id
+        * @throws IOException an error occurred while accessing the required data
+        */
+       private static void processDirectory(final ExifTiffHandler handler,
+               final ByteArrayReader reader, final Set<Integer> processedIfdOffsets,
+               final int ifdOffset, final int tiffHeaderOffset, int inDirectoryId) throws ExifException
+       {
+               // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
+               if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) {
+                       return;
+               }
+
+               // remember that we've visited this directory so that we don't visit it again later
+               processedIfdOffsets.add(ifdOffset);
+
+               if (ifdOffset >= reader.getLength() || ifdOffset < 0) {
+                       //handler.error("Ignored IFD marked to start outside data segment");
+                       return;
+               }
+
+               // First two bytes in the IFD are the number of tags in this directory
+               int dirTagCount = reader.getUInt16(ifdOffset);
+
+               int dirLength = (2 + (12 * dirTagCount) + 4);
+               if (dirLength + ifdOffset > reader.getLength()) {
+                       //handler.error("Illegally sized IFD");
+                       return;
+               }
+
+
+               // Handle each tag in this directory
+               //
+               int invalidTiffFormatCodeCount = 0;
+               for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++)
+               {
+                       final int tagOffset = calculateTagOffset(ifdOffset, tagNumber);
+
+                       // 2 bytes for the tag id
+                       final int childTagId = reader.getUInt16(tagOffset);
+
+                       // 2 bytes for the format code
+                       final int formatCode = reader.getUInt16(tagOffset + 2);
+                       final int componentSizeInBytes = TiffDataFormat.getComponentSize(formatCode);
+
+                       if (componentSizeInBytes == 0)
+                       {
+                               // This error suggests that we are processing at an incorrect index and will generate
+                               // rubbish until we go out of bounds (which may be a while).
+                               if (++invalidTiffFormatCodeCount > 5) {
+                                       //handler.error("Stopping processing as too many errors seen in TIFF IFD");
+                                       return;
+                               }
+                               continue;
+                       }
+
+                       // 4 bytes dictate the number of components in this tag's data
+                       final int componentCount = reader.getInt32(tagOffset + 4);
+                       if (componentCount < 0) {
+                               //handler.error("Negative TIFF tag component count");
+                               continue;
+                       }
+
+                       final int byteCount = componentCount * componentSizeInBytes;
+
+                       final int tagValueOffset;
+                       if (byteCount > 4)
+                       {
+                               // If it's bigger than 4 bytes, the dir entry contains an offset.
+                               final int offsetVal = reader.getInt32(tagOffset + 8);
+                               if (offsetVal + byteCount > reader.getLength()) {
+                                       // Bogus pointer offset and / or byteCount value
+                                       //handler.error("Illegal TIFF tag pointer offset");
+                                       continue;
+                               }
+                               tagValueOffset = tiffHeaderOffset + offsetVal;
+                       }
+                       else {
+                               // 4 bytes or less and value is in the dir entry itself.
+                               tagValueOffset = tagOffset + 8;
+                       }
+
+                       if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {
+                               //handler.error("Illegal TIFF tag pointer offset");
+                               continue;
+                       }
+
+                       // Check that this tag isn't going to allocate outside the bounds of the data array.
+                       // This addresses an uncommon OutOfMemoryError.
+                       if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
+                               //handler.error("Illegal number of bytes for TIFF tag data: " + byteCount);
+                               continue;
+                       }
+
+                       // Special handling for tags that point to other IFDs
+                       if (byteCount == 4 && handler.isTagIfdPointer(childTagId)) {
+                               final int subDirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
+                               processDirectory(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset, childTagId);
+                       }
+                       else if (handler.isInterestingTag(inDirectoryId, childTagId))
+                       {
+                               processTag(handler, childTagId, tagValueOffset, componentCount, formatCode, reader);
+                       }
+               }
+
+               // at the end of each IFD is an optional link to the next IFD
+               final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount);
+               int nextIfdOffset = reader.getInt32(finalTagOffset);
+               if (nextIfdOffset != 0)
+               {
+                       nextIfdOffset += tiffHeaderOffset;
+                       if (nextIfdOffset >= reader.getLength()) {
+                               // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
+                               // Note this could have been caused by jhead 1.3 cropping too much
+                               return;
+                       }
+                       else if (nextIfdOffset < ifdOffset) {
+                               // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory
+                               return;
+                       }
+
+                       processDirectory(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset, inDirectoryId);
+               }
+       }
+
+
+       /**
+        * Process a single tag value
+        */
+       private static void processTag(final ExifTiffHandler handler,
+               final int tagId, final int tagValueOffset,
+               final int componentCount, final int formatCode,
+               final ByteArrayReader reader) throws ExifException
+       {
+               switch (formatCode)
+               {
+                       case TiffDataFormat.CODE_STRING:
+                               handler.setString(tagId, reader.getNullTerminatedString(tagValueOffset, componentCount));
+                               break;
+                       case TiffDataFormat.CODE_RATIONAL_S:
+                               if (componentCount == 1) {
+                                       handler.setRational(tagId, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));
+                               } else if (componentCount > 1) {
+                                       Rational[] array = new Rational[componentCount];
+                                       for (int i = 0; i < componentCount; i++)
+                                               array[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));
+                                       handler.setRationalArray(tagId, array);
+                               }
+                               break;
+                       case TiffDataFormat.CODE_RATIONAL_U:
+                               if (componentCount == 1) {
+                                       handler.setRational(tagId, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
+                               } else if (componentCount > 1) {
+                                       Rational[] array = new Rational[componentCount];
+                                       for (int i = 0; i < componentCount; i++)
+                                               array[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
+                                       handler.setRationalArray(tagId, array);
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT8_S:
+                               if (componentCount == 1) {
+                                       handler.setIntegerValue(tagId, reader.getInt8(tagValueOffset));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT8_U:
+                               if (componentCount == 1) {
+                                       handler.setIntegerValue(tagId, reader.getUInt8(tagValueOffset));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT16_S:
+                               if (componentCount == 1) {
+                                       handler.setIntegerValue(tagId, reader.getInt16(tagValueOffset));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT16_U:
+                               if (componentCount == 1) {
+                                       handler.setIntegerValue(tagId, reader.getUInt16(tagValueOffset));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT32_S:
+                               // NOTE 'long' in this case means 32 bit, not 64
+                               if (componentCount == 1) {
+                                       handler.setIntegerValue(tagId, reader.getInt32(tagValueOffset));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_INT32_U:
+                               // NOTE 'long' in this case means 32 bit, not 64
+                               if (componentCount == 1) {
+                                       handler.setRational(tagId, new Rational(reader.getUInt32(tagValueOffset), 1L));
+                               }
+                               break;
+                       case TiffDataFormat.CODE_SINGLE:
+                       case TiffDataFormat.CODE_DOUBLE:
+                       case TiffDataFormat.CODE_UNDEFINED:
+                       default:
+                               break;
+               }
+       }
+
+       /**
+        * Determine the offset of a given tag within the specified IFD.
+        *
+        * @param ifdStartOffset the offset at which the IFD starts
+        * @param entryNumber    the zero-based entry number
+        */
+       private static int calculateTagOffset(int ifdStartOffset, int entryNumber)
+       {
+               // Add 2 bytes for the tag count.
+               // Each entry is 12 bytes.
+               return ifdStartOffset + 2 + (12 * entryNumber);
+       }
+}
index 063f4941eecd0c59fb8558f1f5a478a6cc0e72da..8864d09ffff35dcb00fee140df0f2d321ed69540 100644 (file)
@@ -82,7 +82,6 @@ function.sendtogps=Stuur data na GPS
 function.exportkml=Voer uit na KML
 function.exportgpx=Voer uit na GPX
 function.exportpov=Voer uit na POV
-function.exportsvg=Voer uit na SVG
 function.exportimage=Voer uit na beeld
 function.editwaypointname=Redigeer baken naam
 function.compress=Kompakteer baan
@@ -118,7 +117,6 @@ function.searchopencachingde=Soek OpenCaching.de
 function.downloadosm=Laai OSM data vir area af
 function.duplicatepoint=Dupliseer punt
 function.setcolours=Stel kleure
-function.setlinewidth=Stel lyn dikte
 function.setlanguage=Stel tale
 function.connecttopoint=Koppel na punt
 function.disconnectfrompoint=Ontkoppel van huidige punt
@@ -261,10 +259,6 @@ dialog.baseimage.zoom=Vergrotings vlak
 dialog.baseimage.incomplete=Beeld onvolledig
 dialog.baseimage.tiles=Te\u00ebls
 dialog.baseimage.size=Beeld groote
-dialog.exportsvg.text=Selekteer die parameters vir die SVG uitvoer
-dialog.exportsvg.phi=Azimuth hoek \u03D5
-dialog.exportsvg.theta=Opstandings angle \u03B8
-dialog.exportsvg.gradients=Grbruik gradient vir skakering
 dialog.exportimage.noimagepossible=Kaart beelde moet gestoor word na skuif sodat dit gebruik kan word vir uitvoer
 dialog.exportimage.drawtrack=Teken baan op kaart
 dialog.exportimage.drawtrackpoints=Teken baan punte
@@ -438,12 +432,12 @@ dialog.deletemarked.nonefound=Geen data punte kon verwyder word
 dialog.pastecoordinates.desc=Sleutel of plak die koordinate hier
 dialog.pastecoordinates.coords=Koordinate
 dialog.pastecoordinates.nothingfound=Gaan asseblief koordinate na en probeer weer
-dialog.help.help=Sien asseblief\n http://gpsprune.activityworkshop.net/\n vir meer inligting en gebruikers handleidings.
+dialog.help.help=Sien asseblief\n https://gpsprune.activityworkshop.net/\n vir meer inligting en gebruikers handleidings.
 dialog.about.version=Weergawe
 dialog.about.build=Bou
 dialog.about.summarytext1=GpsPrune is 'n program vir die laai, vertoon en wysiging van data vanaf GPS ontvangers.
 dialog.about.summarytext2=Dit is vrygestel onder die Gnu GPL gratif, oop w\u00eareldwye gebruik en verbetering.<br>Kopi\u00ebring, herverspreiding en veranderings word toegelaat en aangemoedig<br>volgens die kondisies in the aangegte <code>license.txt</code> le\u00ear.
-dialog.about.summarytext3=Sien asseblief <code style="font-weight:bold"http://activityworkshop.net/</code> vir mer inligting en wenke, insluitend<br> 'n nuwe PDF gebruikers gids wat jy kan koop.
+dialog.about.summarytext3=Sien asseblief <code style="font-weight:bold">https://activityworkshop.net/</code> vir mer inligting en wenke, insluitend<br> 'n nuwe PDF gebruikers gids wat jy kan koop.
 dialog.about.languages=Beskikbare tale
 dialog.about.translatedby=Afrikaans teks deur ...?
 dialog.about.systeminfo=Stelsel informasie
@@ -454,11 +448,6 @@ dialog.about.systeminfo.povray=Povray geinstalleer
 dialog.about.systeminfo.exiftool=Exiftool geinstalleer
 dialog.about.systeminfo.gpsbabel=Gpsbabel geinstalleer
 dialog.about.systeminfo.gnuplot=Gnuplot geinstalleer
-dialog.about.systeminfo.exiflib=Exif biblioteek
-dialog.about.systeminfo.exiflib.internal=Intern
-dialog.about.systeminfo.exiflib.internal.failed=Interne (nie gevind)
-dialog.about.systeminfo.exiflib.external=Ekstern
-dialog.about.systeminfo.exiflib.external.failed=Ekstern (nie gevind)
 dialog.about.yes=Ja
 dialog.about.no=Nee
 dialog.about.credits=Krediete
@@ -477,7 +466,7 @@ dialog.checkversion.newversion1='n nuwe weergawe van GpsPrune is nou beskikbaar!
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Hierdie nuwe weergawe was vrygestel op
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Om die nuwe weergawe aftelaai, gaan na http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Om die nuwe weergawe aftelaai, gaan na https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Jy kan die volgende kortpadsleutels gebruik in plaas van om die muis te gebruik
 dialog.keys.keylist=<table><tr><td>Pylkie sleutels</td><td>Skuif kaart links regs, op, af</td></tr><tr><td>Ctrl + links, regs pylkie</td><td>Selekteer vorige of volgende punt</td></tr><tr><td>Ctrl + op, af pylkie</td><td>Vergroot in of uit</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Selekteer vorige, volgende segment</td></tr><tr><td>Ctrl + Home, Einde</td><td>Selekteer eerste, laaste punt</td></tr><tr><td>Del</td><td>Verwyder huidige punt</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -552,7 +541,7 @@ dialog.diskcache.deleteall=Verwyder alle te\u00ebls
 dialog.diskcache.deleted=%d leers verwyder vanuit datastoor
 dialog.deletefieldvalues.intro=Selekteer die veld om te verwyder vanuit die hudige reeks
 dialog.deletefieldvalues.nofields=Daar is geen velde om te verwyder vir hierdie reeks nie
-dialog.setlinewidth.text=Sleutel die dikte van die lyne om te teken vir die bane (1-4)
+dialog.displaysettings.linewidth=Dikte van die lyne vir die bane (1-4)
 dialog.downloadosm.desc=Bevestig die aflaai van rou OSM data vir die geslekteerde area:
 dialog.searchwikipedianames.search=Soek vir:
 dialog.weather.location=Plek
@@ -786,7 +775,6 @@ wikipedia.lang=en
 openweathermap.lang=en
 webservice.peakfinder=Open Peakfinder.org
 webservice.geohack=Open Geohack bladsy
-webservice.panoramio=Open Panoramio kaart
 
 ## Cardinals for 3d plots
 cardinal.n=N
index c060e880b28a06d9fae18dcca1cf3432b81d1b2d..79627f351b56e0c9de86ddb854de3e8967ebb43b 100644 (file)
@@ -39,7 +39,7 @@ menu.view.browser.yahoo=Mapy Yahoo
 menu.view.browser.bing=Mapy Bing
 menu.settings=Nastaven\u00ed
 menu.settings.onlinemode=Na\u010d\u00edtat mapy z internetu
-menu.settings.antialias=Pou\u017e\u00edt antialiasing
+dialog.displaysettings.antialias=Pou\u017e\u00edt antialiasing
 menu.settings.autosave=P\u0159i ukon\u010den\u00ed automaticky ukl\u00e1dat
 menu.help=Pomoc
 # Popup menu for map
@@ -83,7 +83,6 @@ function.sendtogps=Poslat data do GPS
 function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
-function.exportsvg=Export SVG
 function.exportimage=Export obrazu mapy
 function.editwaypointname=Nastavit n\u00e1zev v\u00fdzna\u010dn\u00e9ho bodu
 function.compress=Komprimovat stopu
@@ -117,7 +116,6 @@ function.mapillary=Hledat fotografie v Mapillary
 function.downloadosm=St\u00e1hnout data OSM pro oblast
 function.duplicatepoint=Zdvojit bod
 function.setcolours=Nastavit barvy
-function.setlinewidth=Nastavit tlou\u0161\u0165ku \u010d\u00e1ry
 function.setlanguage=Nastavit jazyk
 function.connecttopoint=P\u0159ipojit do bodu
 function.disconnectfrompoint=Odpojit od bodu
@@ -259,10 +257,6 @@ dialog.baseimage.zoom=Zv\u011bt\u0161en\u00ed
 dialog.baseimage.incomplete=Obr\u00e1zek ne\u00fapln\u00fd
 dialog.baseimage.tiles=Dla\u017edic
 dialog.baseimage.size=Velikost obr\u00e1zku
-dialog.exportsvg.text=Zvolte parametry exportu do SVG
-dialog.exportsvg.phi=Azimut \u03d5
-dialog.exportsvg.theta=V\u00fd\u0161kov\u00fd \u00fahel \u03b8
-dialog.exportsvg.gradients=Vypl\u0148ovat body barevn\u00fdm p\u0159echodem
 dialog.exportimage.noimagepossible=Aby bylo mo\u017en\u00e9 mapu ulo\u017eit jako obr\u00e1zek, je t\u0159eba st\u00e1hnout a ulo\u017eit dla\u017edice
 dialog.exportimage.drawtrack=Vykreslit linii stopy
 dialog.exportimage.drawtrackpoints=Vykreslit body stopy
@@ -377,9 +371,14 @@ dialog.gpsies.activity.motorbiking=Motorka
 dialog.gpsies.activity.snowshoe=Sn\u011b\u017enice
 dialog.gpsies.activity.sailing=Lo\u010f
 dialog.gpsies.activity.skating=Bruslen\u00ed
+dialog.mapillary.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 fotografie
 dialog.wikipedia.column.name=N\u00e1zev \u010dl\u00e1nku
 dialog.wikipedia.column.distance=Vzd\u00e1lenost
+dialog.wikipedia.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 body
 dialog.wikipedia.gallery=Galerie
+dialog.osmpois.column.name=N\u00e1zev
+dialog.osmpois.column.type=Typ
+dialog.osmpois.nonefound=Nenalezeny \u017e\u00e1dn\u00e9 body
 dialog.correlate.notimestamps=U bod\u016f nejsou \u010dasov\u00e9 zna\u010dky, tak\u017ee nen\u00ed s \u010d\u00edm fotografie sladit.
 dialog.correlate.nouncorrelatedphotos=V\u0161echny fotografie jsou slad\u011bn\u00e9.\nOpravdu chcete pokra\u010dovat?
 dialog.correlate.nouncorrelatedaudios=V\u0161echny audionahr\u00e1vky jsou slad\u011bn\u00e9.\nOpravdu chcete pokra\u010dovat?
@@ -437,12 +436,12 @@ dialog.deletemarked.nonefound=Nemohou b\u00fdt odstran\u011bny \u017e\u00e1dn\u0
 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.help.help=V\u00edce informac\u00ed a tip\u016f (bohu\u017eel nikoli v \u010de\u0161tin\u011b) naleznete na adrese:\n http://gpsprune.activityworkshop.net/ Je tak\u00e9 mo\u017en\u00e9 zakoupit novou u\u017eivatelskou p\u0159\u00edru\u010dku v PDF.
+dialog.help.help=V\u00edce informac\u00ed a tip\u016f (bohu\u017eel nikoli v \u010de\u0161tin\u011b) naleznete na adrese:\n https://gpsprune.activityworkshop.net/ Je tak\u00e9 mo\u017en\u00e9 zakoupit novou u\u017eivatelskou p\u0159\u00edru\u010dku v PDF.
 dialog.about.version=Verze
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune je program k na\u010d\u00edt\u00e1n\u00ed, zobrazov\u00e1n\u00ed a editaci dat z navigac\u00ed GPS.
 dialog.about.summarytext2=Je vyd\u00e1n pod licenc\u00ed GNU GPL, tak\u017ee je celosv\u011btov\u011b zdarma a svobodn\u011b k u\u017e\u00edv\u00e1n\u00ed a vylep\u0161ov\u00e1n\u00ed.<br>Kop\u00edrov\u00e1n\u00ed, redistribuce a \u00fapravy jsou povoleny a podporov\u00e1ny<br>podle podm\u00ednek popsan\u00fdch v souboru <code>licence.txt</code>.
-dialog.about.summarytext3=V\u00edce informac\u00ed a tip\u016f (bohu\u017eel nikoli v \u010de\u0161tin\u011b) naleznete<br> na adrese: <code style="font-weight:bold">http://activityworkshop.net/</code>. Je tak\u00e9 mo\u017en\u00e9 zakoupit novou u\u017eivatelskou p\u0159\u00edru\u010dku v PDF.
+dialog.about.summarytext3=V\u00edce informac\u00ed a tip\u016f (bohu\u017eel nikoli v \u010de\u0161tin\u011b) naleznete<br> na adrese: <code style="font-weight:bold">https://activityworkshop.net/</code>. Je tak\u00e9 mo\u017en\u00e9 zakoupit novou u\u017eivatelskou p\u0159\u00edru\u010dku v PDF.
 dialog.about.languages=Dostupn\u00e9 jazyky
 dialog.about.translatedby=\u010cesk\u00fd p\u0159eklad od prot_d.
 dialog.about.systeminfo=Syst\u00e9mov\u00e9 informace
@@ -453,11 +452,6 @@ dialog.about.systeminfo.povray=Povray nainstalov\u00e1n
 dialog.about.systeminfo.exiftool=Exiftool nainstalov\u00e1n
 dialog.about.systeminfo.gpsbabel=Gpsbabel nainstalov\u00e1n
 dialog.about.systeminfo.gnuplot=Gnuplot nainstalov\u00e1n
-dialog.about.systeminfo.exiflib=Knihovna Exif
-dialog.about.systeminfo.exiflib.internal=Intern\u00ed
-dialog.about.systeminfo.exiflib.internal.failed=Intern\u00ed (nenalezeno)
-dialog.about.systeminfo.exiflib.external=Extern\u00ed
-dialog.about.systeminfo.exiflib.external.failed=Extern\u00ed (nenalezeno)
 dialog.about.yes=Ano
 dialog.about.no=Ne
 dialog.about.credits=Auto\u0159i a spolupracovn\u00edci
@@ -476,7 +470,7 @@ dialog.checkversion.newversion1=Nov\u00e1 verze GpsPrune je u\u017e dostupn\u00e
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Tato verze byla vyd\u00e1na
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Novou verzi m\u016f\u017eete st\u00e1hnout na adrese https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=M\u00edsto my\u0161i m\u016f\u017eete pou\u017e\u00edvat n\u00e1sleduj\u00edc\u00ed kl\u00e1vesov\u00e9 zkratky
 dialog.keys.keylist=<table><tr><td>\u0160ipky</td><td>Posunout mapu vlevo, vpravo, nahoru, dol\u016f</td></tr><tr><td>Ctrl + \u0161ipka vlevo, vpravo</td><td>Vybrat p\u0159edchoz\u00ed nebo n\u00e1sleduj\u00edc\u00ed bod</td></tr><tr><td>Ctrl + \u0161ipka nahoru, dol\u016f</td><td>P\u0159ibl\u00ed\u017eit, odd\u00e1lit</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Vybrat p\u0159edchoz\u00ed, n\u00e1sleduj\u00edc\u00ed \u010d\u00e1st stopy</td></tr><tr><td>Ctrl + Home, End</td><td>Vybrat prvn\u00ed, posledn\u00ed bod</td></tr><tr><td>Del</td><td>Smazat aktu\u00e1ln\u00ed bod</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -542,8 +536,10 @@ dialog.diskcache.deleteall=Smazat v\u0161echny soubory
 dialog.diskcache.deleted=Smaz\u00e1no %d soubor\u016f z cache
 dialog.deletefieldvalues.intro=Vyberte pole, kter\u00e9 se m\u00e1 z aktu\u00e1ln\u00edho rozmez\u00ed odstranit
 dialog.deletefieldvalues.nofields=V tomto rozmez\u00ed nelze smazat \u017e\u00e1dn\u00e9 pole
-dialog.setlinewidth.text=Zvolte tlou\u0161\u0165ku \u010d\u00e1ry, kterou se nakresl\u00ed stopa (1-4)
+dialog.displaysettings.linewidth=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.displaysettings.wpicon.plectrum=Trs\u00e1tko
+dialog.displaysettings.wpicon.ring=Krou\u017Eek
 dialog.searchwikipedianames.search=Vyhledat:
 dialog.weather.location=M\u00edsto
 dialog.weather.update=Posledn\u00ed aktualizace
@@ -776,7 +772,6 @@ wikipedia.lang=cs
 openweathermap.lang=en
 webservice.peakfinder=Otev\u0159\u00edt Peakfinder.org
 webservice.geohack=Otev\u0159\u00edt Geohack
-webservice.panoramio=Otev\u0159\u00edt Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -834,7 +829,6 @@ error.jpegload.dialogtitle=Chyba p\u0159i na\u010d\u00edt\u00e1n\u00ed fotografi
 error.jpegload.nofilesfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory
 error.jpegload.nojpegsfound=Nenalezeny \u017e\u00e1dn\u00e9 soubory jpeg
 error.jpegload.nogpsfound=Nenalezena informace GPS
-error.jpegload.exifreadfailed=Nepoda\u0159ilo se na\u010d\u00edst informaci Exif. Tu nelze na\u010d\u00edst\nbez intern\u00ed nebo extern\u00ed knihovny.
 error.audioload.nofilesfound=Nebyly nalezeny \u017e\u00e1dn\u00e9 zvukov\u00e9 soubory.
 error.gpsload.unknown=Nezn\u00e1m\u00e1 chyba
 error.undofailed.title=Selhalo undo
index e1d29ae25c2edf21bfa7e63643c545b34fe70f15..e975e29317d5d167a07ecac6b3022df95a89df6c 100644 (file)
@@ -63,7 +63,6 @@ function.sendtogps=Overf\u00f8r data til GPS
 function.exportkml=Eksport\u00e9r KML
 function.exportgpx=Eksport\u00e9r GPX
 function.exportpov=Eksport\u00e9r POV
-function.exportsvg=Eksport\u00e9r SVG
 function.editwaypointname=Ret waypoint-navn
 function.compress=Komprim\u00e9r spor
 function.deleterange=Fjern det valgte omr\u00e5de
index 26a3df32cd414b977670891e3184e6c7c76e0d1a..bce51c62bb8f8a3b0545bdacb96e9ab53b69456c 100644 (file)
@@ -12,8 +12,6 @@ menu.track=Track
 menu.track.undo=R\u00fcckg\u00e4ngig
 menu.track.clearundo=Liste der letzten \u00c4nderungen l\u00f6schen
 menu.track.markrectangle=Punkte im Viereck markieren
-function.deletemarked=Markierte Punkte l\u00f6schen
-function.rearrangewaypoints=Wegpunkte neu anordnen
 menu.range=Bereich
 menu.range.all=Alles markieren
 menu.range.none=Nichts markieren
@@ -39,7 +37,6 @@ menu.view.browser.yahoo=Yahoo Maps
 menu.view.browser.bing=Bing Maps
 menu.settings=Einstellungen
 menu.settings.onlinemode=Karten aus dem Internet laden
-menu.settings.antialias=Kantengl\u00e4ttung an
 menu.settings.autosave=Einstellungen automatisch speichern
 menu.help=Hilfe
 # Popup menu for map
@@ -83,11 +80,12 @@ function.sendtogps=Zum GPS schicken
 function.exportkml=KML exportieren
 function.exportgpx=GPX exportieren
 function.exportpov=POV exportieren
-function.exportsvg=SVG exportieren
 function.exportimage=Bild exportieren
 function.editwaypointname=Namen des Punkts bearbeiten
 function.compress=Track komprimieren
 function.marklifts=Bergbahnen markieren
+function.deletemarked=Markierte Punkte l\u00f6schen
+function.rearrangewaypoints=Wegpunkte neu anordnen
 function.deleterange=Bereich l\u00f6schen
 function.croptrack=Track zuschneiden
 function.interpolate=Punkte interpolieren
@@ -110,18 +108,20 @@ function.setpaths=Programmpfade setzen
 function.selectsegment=Aktuellen Abschnitt markieren
 function.splitsegments=In Trackabschnitte schneiden
 function.sewsegments=Trackabschnitte zusammenf\u00fcgen
+function.createmarkerwaypoints=Wegpunkte im bestimmten Abstand kreieren
 function.getgpsies=Tracks bei GPSies.com herunterladen
 function.uploadgpsies=Track zu GPSies.com hochladen
 function.lookupsrtm=H\u00f6hendaten von SRTM nachschlagen
 function.downloadsrtm=SRTM Dateien herunterladen
 function.getwikipedia=Wikipediaartikel in der N\u00e4he nachschlagen
 function.searchwikipedianames=Wikipedia nach Namen durchsuchen
+function.searchosmpois=OpenStreetMap nach Punkten durchsuchen
 function.searchopencachingde=OpenCaching.de durchsuchen
 function.mapillary=Mapillary nach Fotos durchsuchen
 function.downloadosm=OSM-Daten f\u00fcr dieses Gebiet herunterladen
 function.duplicatepoint=Punkt verdoppeln
 function.setcolours=Farben einstellen
-function.setlinewidth=Liniendicke einstellen
+function.setdisplaysettings=Darstellungsoptionen
 function.setlanguage=Sprache einstellen
 function.connecttopoint=Mit Punkt verkn\u00fcpfen
 function.disconnectfrompoint=Vom Punkt trennen
@@ -146,6 +146,7 @@ function.diskcache=Karten auf Festplatte speichern
 function.managetilecache=Kartenkacheln verwalten
 function.getweatherforecast=Wettervorhersage herunterladen
 function.setaltitudetolerance=Altitudtoleranz einstellen
+function.selecttimezone=Zeitzone bestimmen
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune beenden
@@ -264,10 +265,6 @@ dialog.baseimage.zoom=Zoomstufe
 dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
 dialog.baseimage.tiles=Kacheln
 dialog.baseimage.size=Bildgr\u00f6\u00dfe
-dialog.exportsvg.text=W\u00e4hlen Sie die Parameter f\u00fcr den SVG-Export aus
-dialog.exportsvg.phi=Richtungswinkel \u03d5
-dialog.exportsvg.theta=Neigungswinkel \u03b8
-dialog.exportsvg.gradients=Farbverl\u00e4ufe verwenden
 dialog.exportimage.noimagepossible=Kartenbilder m\u00fcssen schon gespeichert werden bevor sie in einem Export verwendet werden k\u00f6nnen
 dialog.exportimage.drawtrack=Track auf der Karte zeichnen
 dialog.exportimage.drawtrackpoints=Trackpunkte zeichnen
@@ -387,6 +384,9 @@ dialog.wikipedia.column.name=Artikelname
 dialog.wikipedia.column.distance=Entfernung
 dialog.wikipedia.nonefound=Keine Punkte gefunden
 dialog.wikipedia.gallery=Bilder
+dialog.osmpois.column.name=Name
+dialog.osmpois.column.type=Punkttyp
+dialog.osmpois.nonefound=Keine Punkte gefunden
 dialog.geocaching.nonefound=Keine Punkte gefunden
 dialog.correlate.notimestamps=Die Punkte enthalten keine Zeitangaben, deshalb k\u00f6nnen die Fotos nicht zugeordnet werden.
 dialog.correlate.nouncorrelatedphotos=Alle Fotos sind schon zugeordnet.\nWollen Sie trotzdem fortfahren?
@@ -446,12 +446,12 @@ dialog.deletemarked.nonefound=Es konnten keine Punkte entfernt werden
 dialog.pastecoordinates.desc=Koordinaten eingeben oder einf\u00fcgen
 dialog.pastecoordinates.coords=Koordinaten
 dialog.pastecoordinates.nothingfound=Bitte pr\u00fcfen Sie die Koordinaten und versuchen Sie es nochmals
-dialog.help.help=Weitere Informationen und Benutzeranleitungen finden Sie unter\n http://gpsprune.activityworkshop.net/
+dialog.help.help=Weitere Informationen und Benutzeranleitungen finden Sie unter\n https://gpsprune.activityworkshop.net/
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune ist ein Programm zum Laden, Darstellen und Editieren der Daten von GPS- Ger\u00e4ten.
 dialog.about.summarytext2=Es wird unter der Gnu GPL zur Verf\u00fcgung gestellt zum freien, kostenlosen und offenen Gebrauch und zur Weiterentwicklung.<br>Das Kopieren, Weiterverbreiten und Ver\u00e4ndern ist erlaubt und willkommen<br>unter den in der Datei <code>license.txt</code> enthaltenen Bedingungen.
-dialog.about.summarytext3=Auf der Seite <code style="font-weight:bold">http://activityworkshop.net/</code> finden Sie weitere Informationen und Bedienungsanleitungen.
+dialog.about.summarytext3=Auf der Seite <code style="font-weight:bold">https://activityworkshop.net/</code> finden Sie weitere Informationen und Bedienungsanleitungen.
 dialog.about.languages=Verf\u00fcgbare Sprachen
 dialog.about.translatedby=Deutsche \u00dcbersetzung von activityworkshop.
 dialog.about.systeminfo=System-Informationen
@@ -462,11 +462,6 @@ dialog.about.systeminfo.povray=Povray installiert
 dialog.about.systeminfo.exiftool=ExifTool installiert
 dialog.about.systeminfo.gpsbabel=GPSBabel installiert
 dialog.about.systeminfo.gnuplot=Gnuplot installiert
-dialog.about.systeminfo.exiflib=Exif-Bibliothek
-dialog.about.systeminfo.exiflib.internal=Intern
-dialog.about.systeminfo.exiflib.internal.failed=Intern (nicht gefunden)
-dialog.about.systeminfo.exiflib.external=Extern
-dialog.about.systeminfo.exiflib.external.failed=Extern (nicht gefunden)
 dialog.about.yes=Ja
 dialog.about.no=Nein
 dialog.about.credits=Danksagung
@@ -485,7 +480,7 @@ dialog.checkversion.newversion1=Eine neue Version von GpsPrune ist jetzt verf\u0
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Diese neue Version ist am
 dialog.checkversion.releasedate2=ver\u00f6ffentlicht worden.
-dialog.checkversion.download=Um die neue Version herunterzuladen, gehen Sie zu http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Um die neue Version herunterzuladen, gehen Sie zu https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Anstelle der Maus k\u00f6nnen Sie folgende Tastenkombinationen nutzen
 dialog.keys.keylist=<table><tr><td>Pfeil Tasten</td><td>Karte verschieben</td></tr><tr><td>Strg + Links-, Rechts-Pfeil</td><td>Vorherigen oder n\u00e4chsten Punkt markieren</td></tr><tr><td>Strg + Auf-, Abw\u00e4rts-Pfeil</td><td>Ein- oder Auszoomen</td></tr><tr><td>Strg + Bild auf, ab</td><td>Zum vorherigen oder n\u00e4chsten Abschnitt</td></tr><tr><td>Strg + Pos1, Ende</td><td>Ersten oder letzten Punkt markieren</td></tr><tr><td>Entf</td><td>Aktuellen Punkt entfernen</td></tr></table>
 dialog.keys.normalmodifier=Strg
@@ -564,7 +559,17 @@ dialog.diskcache.deleteall=Alle Kacheln l\u00f6schen
 dialog.diskcache.deleted=Es wurden %d Dateien aus dem Ordner gel\u00f6scht
 dialog.deletefieldvalues.intro=W\u00e4hlen Sie das Feld aus, das Sie l\u00f6schen m\u00f6chten
 dialog.deletefieldvalues.nofields=Es sind keine Felder zu l\u00f6schen f\u00fcr diesen Bereich
-dialog.setlinewidth.text=Geben Sie die Dicke der Linien ein (1-4)
+dialog.displaysettings.linewidth=Dicke der Linien in Pixeln (1-4)
+dialog.displaysettings.antialias=Kantengl\u00e4ttung an
+dialog.displaysettings.waypointicons=Wegpunkt Ikons
+dialog.displaysettings.wpicon.default=Punkt
+dialog.displaysettings.wpicon.ringpt=Rundes Schild
+dialog.displaysettings.wpicon.plectrum=Plektrum
+dialog.displaysettings.wpicon.ring=Kreis
+dialog.displaysettings.wpicon.pin=Sto\u00dfnadel
+dialog.displaysettings.size.small=Klein
+dialog.displaysettings.size.medium=Mittel
+dialog.displaysettings.size.large=Gro\u00df
 dialog.downloadosm.desc=Die OpenStreetMap-Daten f\u00fcr das folgende Gebiet werden heruntergeladen (.osm-Datei):
 dialog.searchwikipedianames.search=Suche nach:
 dialog.weather.location=Ort
@@ -596,6 +601,12 @@ dialog.deletebydate.column.keep=Behalten
 dialog.deletebydate.column.delete=L\u00f6schen
 dialog.setaltitudetolerance.text.metres=Mindestabweichung (Meter) f\u00fcr H\u00f6henunterschiede
 dialog.setaltitudetolerance.text.feet=Mindestabweichung (Feet) f\u00fcr H\u00f6henunterschiede
+dialog.settimezone.intro=Hier k\u00f6nnen Sie die Zeitzone ausw\u00e4hlen, in der die Punkt Zeitstempeln angezeigt werden
+dialog.settimezone.system=Zeitzone des Systems verweden
+dialog.settimezone.custom=Folgende Zeitzone verwenden:
+dialog.settimezone.list.toomany=Zu viele zum Anzeigen
+dialog.settimezone.selectedzone=Ausgew\u00e4hlte Zeitzone
+dialog.settimezone.offsetfromutc=Unterschied zur UTC
 dialog.autoplay.duration=Abspieldauer (Sek)
 dialog.autoplay.usetimestamps=Zeitstempeln verwenden
 dialog.autoplay.rewind=Zum Anfang
@@ -806,7 +817,6 @@ wikipedia.lang=de
 openweathermap.lang=de
 webservice.peakfinder=Peakfinder.org \u00f6ffnen
 webservice.geohack=Geohack-Seite \u00f6ffnen
-webservice.panoramio=Panoramio Karte \u00f6ffnen
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -864,7 +874,6 @@ error.jpegload.dialogtitle=Fehler beim Laden von Fotos
 error.jpegload.nofilesfound=Keine Dateien gefunden
 error.jpegload.nojpegsfound=Keine JPG-Dateien gefunden
 error.jpegload.nogpsfound=Keine GPS-Information gefunden
-error.jpegload.exifreadfailed=Exif-Aufruf fehlgeschlagen. Exif-Information k\u00f6nnen nicht gelesen werden\nwenn nicht eine interne oder externe Bibliothek vorhanden ist.
 error.audioload.nofilesfound=Keine Audiodateien gefunden
 error.gpsload.unknown=Unbekannter Fehler
 error.undofailed.title=Undo fehlgeschlagen
index 9697e39fb2a53d7cbb5332c0c2d89cffb140b8bc..d7deef6cc69fb26f57f9758f5ca6ada7b1c8fd03 100644 (file)
@@ -38,7 +38,7 @@ menu.view.showsidebars=Seiteleischten aazeige
 menu.view.browser=Karte inem Browser
 menu.settings=Iistellige
 menu.settings.onlinemode=Karten uusem Internet lade
-menu.settings.antialias=Kantegl\u00e4ttig aa
+dialog.displaysettings.antialias=Kantegl\u00e4ttig aa
 menu.settings.autosave=Iistellige automatisch speichere
 menu.help=Hilfe
 # Popup menu for map
@@ -82,7 +82,6 @@ function.sendtogps=zum GPS schicke
 function.exportkml=KML exportier\u00e4
 function.exportgpx=GPX exportier\u00e4
 function.exportpov=POV exportier\u00e4
-function.exportsvg=SVG exportier\u00e4
 function.exportimage=Bild exportier\u00e4
 function.editwaypointname=Waypoint Namen editiere
 function.compress=Track komprimier\u00e4
@@ -107,14 +106,16 @@ function.setmapbg=Karte Hintegrund setz\u00e4
 function.selectsegment=Aktuelli Segm\u00e4nt selektiere
 function.splitsegments=In Tracksegm\u00e4nte schniide
 function.sewsegments=Tracksegm\u00e4nte z\u00e4mef\u00fcge
+function.createmarkerwaypoints=Waypoints inem bestimmten Abstand kreiere
 function.getgpsies=Gpsies Tracks hol\u00e4
 function.uploadgpsies=Date zum Gpsies uufalad\u00e4
 function.lookupsrtm=H\u00f6hendate vonem SRTM hole
 function.downloadsrtm=SRTM Files abalade
 function.getwikipedia=Im Wikipedia in dr N\u00f6chi naaluege
 function.searchwikipedianames=Wikipedia nachem Namen durasueche
+function.searchosmpois=OpenStreetMap na P\u00fcnkt durasueche
 function.searchopencachingde=OpenCaching.de durasueche
-function.mapillary=Mapillary nach F\u00f6telis durasueche
+function.mapillary=Mapillary na F\u00f6telis durasueche
 function.downloadosm=OSM-Date f\u00fcr dere Gebiet abalad\u00e4
 function.duplicatepoint=Punkt verdoppl\u00e4
 function.correlatephotos=F\u00f6telis korrelier\u00e4
@@ -130,17 +131,18 @@ function.playaudio=Audiofile abspiel\u00e4
 function.stopaudio=Abspielen abbr\u00e4ch\u00e4
 function.setpaths=Programmepfade setz\u00e4
 function.setcolours=Farben setz\u00e4
-function.setlinewidth=Liniedicke setz\u00e4
+function.setdisplaysettings=Darstelligsoptione
 function.setlanguage=Sproch setz\u00e4
 function.help=Hilfe
-function.showkeys=Tastekombinatione aazeige
+function.showkeys=Tastekombinatione aazeig\u00e4
 function.about=\u00dcber GpsPrune
 function.checkversion=Pruef nach ne noie Version
-function.saveconfig=Iistellige speichere
-function.diskcache=Karten uufem Disk speichere
-function.managetilecache=Kartebildli verwolte
-function.getweatherforecast=W\u00e4tterprognose abalade
-function.setaltitudetolerance=H\u00f6chitoleranz iistelle
+function.saveconfig=Iistellige speicher\u00e4
+function.diskcache=Karten uufem Disk speicher\u00e4
+function.managetilecache=Kartebildli verwolt\u00e4
+function.getweatherforecast=W\u00e4tterprognose abalad\u00e4
+function.setaltitudetolerance=H\u00f6chitoleranz iistell\u00e4
+function.selecttimezone=Ziitzone bstimm\u00e4
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune be\u00e4nde
@@ -237,7 +239,7 @@ dialog.exportgpx.desc=Beschriibig
 dialog.exportgpx.includetimestamps=Au Ziitst\u00e4mpel
 dialog.exportgpx.copysource=Xml-Qu\u00e4lle kopier\u00e4
 dialog.exportgpx.encoding=Enkodierig
-dialog.exportgpx.encoding.system=System
+dialog.exportgpx.encoding.system=Syschtem
 dialog.exportgpx.encoding.utf8=UTF-8
 dialog.exportpov.text=G\u00e4bet Sie die Parameter ii f\u00fcrs POV Export
 dialog.exportpov.font=Font
@@ -259,10 +261,6 @@ dialog.baseimage.zoom=Zoomstufe
 dialog.baseimage.incomplete=Bild unvollst\u00e4ndig
 dialog.baseimage.tiles=Kachle
 dialog.baseimage.size=Bildgr\u00f6ssi
-dialog.exportsvg.text=W\u00e4hlet Sie die Parameter f\u00fcrs SVG Export uus
-dialog.exportsvg.phi=Richtigswinkel \u03D5
-dialog.exportsvg.theta=Neigigswinkel \u03B8
-dialog.exportsvg.gradients=Farbeverl\u00e4ufe verw\u00e4nde
 dialog.exportimage.noimagepossible=Kartebilder m\u00fcsset scho gspeicheret werde, bevor sie bim Export verwendet werde k\u00f6nne
 dialog.exportimage.drawtrack=Track uf d Karte zeichne
 dialog.exportimage.drawtrackpoints=Trackp\u00fcnkte au zeichne
@@ -382,6 +380,9 @@ dialog.wikipedia.column.name=Artikelname
 dialog.wikipedia.column.distance=Entf\u00e4rnig
 dialog.wikipedia.nonefound=Kei Wiki-Iitr\u00e4ge gfunde
 dialog.wikipedia.gallery=Fotis
+dialog.osmpois.column.name=Name
+dialog.osmpois.column.type=Punkttyp
+dialog.osmpois.nonefound=Kei P\u00fcnkt gfunde
 dialog.geocaching.nonefound=Kei C\u00e4ches gfunde
 dialog.correlate.notimestamps=Es h\u00e4t kei Ziitst\u00e4mpel inem Track inn\u00e4, so s'isch n\u00f6d m\u00f6glech die F\u00f6telis zu korrelier\u00e4.
 dialog.correlate.nouncorrelatedphotos=Alle F\u00f6telis sin scho korreliert.\nWend Sie trotzdem fortsetz\u00e4?
@@ -441,27 +442,22 @@ dialog.deletemarked.nonefound=Kei P\u00fcnkte h\u00e4tte gel\u00f6scht werde k\u
 dialog.pastecoordinates.desc=G\u00e4bet Sie hier die Koordinaten inn\u00e4
 dialog.pastecoordinates.coords=Koordinate
 dialog.pastecoordinates.nothingfound=Pr\u00fcefet Sie die Koordinate und versuechet nomal
-dialog.help.help=Lueget Sie na\n http://gpsprune.activityworkshop.net/\nf\u00fcr wiitere Information und Benutzeraaleitige.
+dialog.help.help=Lueget Sie na\n https://gpsprune.activityworkshop.net/\nf\u00fcr wiitere Information und Benutzeraaleitige.
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune isch s Programm f\u00fcrs Lade, Darstelle und Editiere vo Date von GPS Ger\u00e4te.
 dialog.about.summarytext2=Es isch unter den Gnu GPL zur Verf\u00fcegig gstellt,f\u00fcr frei, gratis und offen Gebruuch und Wiiterentwicklig.<br>Kopiere, Wiiterverbreitig und Ver\u00e4nderige sin erlaubt und willkomme<br>unter die Bedingige im enthaltene <code>license.txt</code> File.
-dialog.about.summarytext3=Bitte lueget Sie na <code style="font-weight:bold">http://activityworkshop.net/</code> f\u00fcr wiitere Informatione und Benutzeraaleitige.
+dialog.about.summarytext3=Bitte lueget Sie na <code style="font-weight:bold">https://activityworkshop.net/</code> f\u00fcr wiitere Informatione und Benutzeraaleitige.
 dialog.about.languages=Verf\u00fcegbare Sproche
 dialog.about.translatedby=Schwiizerd\u00fc\u00fctschi \u00dcbersetzig vo activityworkshop.
 dialog.about.systeminfo=Syschtem Info
 dialog.about.systeminfo.os=Betriebsyschtem
 dialog.about.systeminfo.java=Version vonem Java
-dialog.about.systeminfo.java3d=Java3d inschtalliert
-dialog.about.systeminfo.povray=Povray inschtalliert
-dialog.about.systeminfo.exiftool=Exiftool inschtalliert
-dialog.about.systeminfo.gpsbabel=Gpsbabel inschtalliert
-dialog.about.systeminfo.gnuplot=Gnuplot inschtalliert
-dialog.about.systeminfo.exiflib=Exif Bibliothek
-dialog.about.systeminfo.exiflib.internal=Intern
-dialog.about.systeminfo.exiflib.internal.failed=Intern (n\u00f6d gfunde)
-dialog.about.systeminfo.exiflib.external=Ext\u00e4rn
-dialog.about.systeminfo.exiflib.external.failed=Ext\u00e4rn (n\u00f6d gfunde)
+dialog.about.systeminfo.java3d=Java3d installiert
+dialog.about.systeminfo.povray=Povray installiert
+dialog.about.systeminfo.exiftool=Exiftool installiert
+dialog.about.systeminfo.gpsbabel=Gpsbabel installiert
+dialog.about.systeminfo.gnuplot=Gnuplot installiert
 dialog.about.yes=Ja
 dialog.about.no=Nei
 dialog.about.credits=Credits
@@ -480,7 +476,7 @@ dialog.checkversion.newversion1=Ne noii Version vonem GpsPrune isch jetzt usse!
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Die noii Version isch am
 dialog.checkversion.releasedate2=ussecho.
-dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Um die noii Version runterzlade, schauet Sie na https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Aastatt d'Muus k\u00f6nnet Sie diese Tastekombinationen nutze
 dialog.keys.keylist=<table><tr><td>Pfiil Taste</td><td>Karte verschiebe</td></tr><tr><td>Strg + links, r\u00e4chts Pfiil</td><td>Vorherigi oder n\u00f6chsti Punkt markiere</td></tr><tr><td>Strg + uuf, aba Pfiil</td><td>Ii- oder Uusezoome</td></tr><tr><td>Strg + Bild uuf, ab</td><td>Vorherigi oder n\u00f6chsti Segm\u00e4nt markiere</td></tr><tr><td>Strg + Pos1, \u00c4nde</td><td>Erschti oder letschti Punkt markiere</td></tr><tr><td>Entf</td><td>Aktuelli Punkt l\u00f6sche</td></tr></table>
 dialog.keys.normalmodifier=Strg
@@ -559,7 +555,16 @@ dialog.diskcache.deleteall=Alli Kachle l\u00f6sche
 dialog.diskcache.deleted=Es sin %d Files uusem Ordner gl\u00f6scht worde
 dialog.deletefieldvalues.intro=W\u00e4hlet Sie s F\u00e4ld uus zum l\u00f6sche
 dialog.deletefieldvalues.nofields=Es sin kei F\u00e4lder z'l\u00f6sche f\u00fcr dere Beriich
-dialog.setlinewidth.text=G\u00e4bet Sie die Dicke vonen Linien ii (1-4)
+dialog.displaysettings.linewidth=Dicke vonen Linien in Pixeln (1-4)
+dialog.displaysettings.waypointicons=Waypoint Ikons
+dialog.displaysettings.wpicon.default=P\u00fcnktli
+dialog.displaysettings.wpicon.ringpt=Rundes Schild
+dialog.displaysettings.wpicon.plectrum=Plektrum
+dialog.displaysettings.wpicon.ring=Chreis
+dialog.displaysettings.wpicon.pin=Sto\u00dfnadel
+dialog.displaysettings.size.small=Chli
+dialog.displaysettings.size.medium=Mittel
+dialog.displaysettings.size.large=Gross
 dialog.downloadosm.desc=Best\u00e4tige um rohi OSM Date f\u00fcrn Gebiet aba zlade:
 dialog.searchwikipedianames.search=Sueche na:
 dialog.weather.location=Ort
@@ -591,6 +596,12 @@ dialog.deletebydate.column.keep=Behalte
 dialog.deletebydate.column.delete=L\u00f6sche
 dialog.setaltitudetolerance.text.metres=Mindeschtabweichig (Meter) f\u00fcr H\u00f6chiunterschied
 dialog.setaltitudetolerance.text.feet=Mindeschtabweichig (Feet) f\u00fcr H\u00f6chiunterschied
+dialog.settimezone.intro=Do k\u00f6nnet Sie d Ziitzone uusw\u00e4hle, f\u00fcr d'aazeig vo P\u00fcnkt Ziitst\u00e4mple
+dialog.settimezone.system=Ziitzone vonem Syschtem
+dialog.settimezone.custom=Folgendi Ziitzone:
+dialog.settimezone.list.toomany=Viu z'viu
+dialog.settimezone.selectedzone=Uusgw\u00e4hlti Ziitzone
+dialog.settimezone.offsetfromutc=Unterschied vo UTC
 dialog.autoplay.duration=Abspieldauer (Sek)
 dialog.autoplay.usetimestamps=Ziitst\u00e4mple verw\u00e4nde
 dialog.autoplay.rewind=Zum Aafang
@@ -801,7 +812,6 @@ wikipedia.lang=als
 openweathermap.lang=de
 webservice.peakfinder=Peakfinder.org \u00f6ffne
 webservice.geohack=Geohack-Siite \u00f6ffne
-webservice.panoramio=Panoramio Karte \u00f6ffne
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -859,7 +869,6 @@ error.jpegload.dialogtitle=F\u00e4hle bim Lade von F\u00f6telis
 error.jpegload.nofilesfound=Kei Files gfunde
 error.jpegload.nojpegsfound=Kei Jpegs gfunde
 error.jpegload.nogpsfound=Kei GPS Information gfunde
-error.jpegload.exifreadfailed=Exif Uufruef isch fehlgschlage. Kei Exif Infos k\u00f6nnet gl\u00e4se werde\nohni nen interni oder ext\u00e4rni Bibliothek.
 error.audioload.nofilesfound=Kei Audiofiles gfunde
 error.gpsload.unknown=Unbekannts F\u00e4hler
 error.undofailed.title=Undo isch fehlgschlage worde
index 489b5c677fe902a09f86f6df1e87e404e50848fd..9e3b7cd57e1b8aab1dd1fd41a9041513b8208f70 100644 (file)
@@ -12,7 +12,6 @@ menu.track=Track
 menu.track.undo=Undo
 menu.track.clearundo=Clear undo list
 menu.track.markrectangle=Mark points in rectangle
-function.deletemarked=Delete marked points
 menu.range=Range
 menu.range.all=Select all
 menu.range.none=Select none
@@ -38,7 +37,6 @@ menu.view.browser.yahoo=Yahoo maps
 menu.view.browser.bing=Bing maps
 menu.settings=Settings
 menu.settings.onlinemode=Load maps from internet
-menu.settings.antialias=Use antialiasing
 menu.settings.autosave=Autosave settings on exit
 menu.help=Help
 # Popup menu for map
@@ -82,12 +80,12 @@ function.sendtogps=Send data to GPS
 function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
-function.exportsvg=Export SVG
 function.exportimage=Export image
 function.editwaypointname=Edit waypoint name
 function.compress=Compress track
 function.marklifts=Mark uphill lifts
 function.deleterange=Delete range
+function.deletemarked=Delete marked points
 function.croptrack=Crop track
 function.interpolate=Interpolate points
 function.deletebydate=Delete points by date
@@ -108,12 +106,14 @@ function.autoplay=Autoplay track
 function.selectsegment=Select current segment
 function.splitsegments=Split track into segments
 function.sewsegments=Sew track segments together
+function.createmarkerwaypoints=Create marker waypoints
 function.getgpsies=Get Gpsies tracks
 function.uploadgpsies=Upload track to Gpsies
 function.lookupsrtm=Get altitudes from SRTM
 function.downloadsrtm=Download SRTM tiles
 function.getwikipedia=Get nearby Wikipedia articles
 function.searchwikipedianames=Search Wikipedia by name
+function.searchosmpois=Get nearby OSM points
 function.searchopencachingde=Search OpenCaching.de
 function.mapillary=Search for photos in Mapillary
 function.downloadosm=Download OSM data for area
@@ -135,7 +135,7 @@ function.stopaudio=Stop audio clip
 function.setmapbg=Set map background
 function.setpaths=Set program paths
 function.setcolours=Set colours
-function.setlinewidth=Set line width
+function.setdisplaysettings=Set display options
 function.setlanguage=Set language
 function.help=Help
 function.showkeys=Show shortcut keys
@@ -146,6 +146,7 @@ function.diskcache=Save maps to disk
 function.managetilecache=Manage tile cache
 function.getweatherforecast=Get weather forecast
 function.setaltitudetolerance=Set altitude tolerance
+function.selecttimezone=Set timezone
 
 # Dialogs
 dialog.exit.confirm.title=Exit GpsPrune
@@ -174,6 +175,7 @@ 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.intro=
 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.
@@ -264,10 +266,6 @@ dialog.baseimage.zoom=Zoom level
 dialog.baseimage.incomplete=Image incomplete
 dialog.baseimage.tiles=Tiles
 dialog.baseimage.size=Image size
-dialog.exportsvg.text=Select the parameters for the SVG export
-dialog.exportsvg.phi=Azimuth angle \u03D5
-dialog.exportsvg.theta=Elevation angle \u03B8
-dialog.exportsvg.gradients=Use gradients for shading
 dialog.exportimage.noimagepossible=Map images need to be cached to disk in order to use them for an export.
 dialog.exportimage.drawtrack=Draw track on map
 dialog.exportimage.drawtrackpoints=Draw track points
@@ -387,6 +385,9 @@ dialog.wikipedia.column.name=Article name
 dialog.wikipedia.column.distance=Distance
 dialog.wikipedia.nonefound=No wikipedia entries found
 dialog.wikipedia.gallery=Gallery
+dialog.osmpois.column.name=Name
+dialog.osmpois.column.type=Type
+dialog.osmpois.nonefound=No points found
 dialog.geocaching.nonefound=No geocaches found
 dialog.correlate.notimestamps=There are no timestamps in the data points, so there is nothing to correlate with the photos.
 dialog.correlate.nouncorrelatedphotos=There are no uncorrelated photos.\nAre you sure you want to continue?
@@ -446,12 +447,12 @@ dialog.deletemarked.nonefound=No data points could be removed
 dialog.pastecoordinates.desc=Enter or paste the coordinates here
 dialog.pastecoordinates.coords=Coordinates
 dialog.pastecoordinates.nothingfound=Please check the coordinates and try again
-dialog.help.help=Please see\n http://gpsprune.activityworkshop.net/\nfor more information and tips,\nincluding a PDF user guide you can buy.
+dialog.help.help=Please see\n https://gpsprune.activityworkshop.net/\nfor more information and tips,\nincluding a PDF user guide you can buy.
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune is a program for loading, displaying and editing data from GPS receivers.
 dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.<br>Copying, redistribution and modification are permitted and encouraged<br>according to the conditions in the included <code>license.txt</code> file.
-dialog.about.summarytext3=Please see <code style="font-weight:bold">http://activityworkshop.net/</code> for more information and tips, including<br>a PDF user guide you can buy.
+dialog.about.summarytext3=Please see <code style="font-weight:bold">https://activityworkshop.net/</code> for more information and tips, including<br>a PDF user guide you can buy.
 dialog.about.languages=Available languages
 dialog.about.translatedby=English text by activityworkshop.
 dialog.about.systeminfo=System info
@@ -462,11 +463,6 @@ dialog.about.systeminfo.povray=Povray installed
 dialog.about.systeminfo.exiftool=Exiftool installed
 dialog.about.systeminfo.gpsbabel=Gpsbabel installed
 dialog.about.systeminfo.gnuplot=Gnuplot installed
-dialog.about.systeminfo.exiflib=Exif library
-dialog.about.systeminfo.exiflib.internal=Internal
-dialog.about.systeminfo.exiflib.internal.failed=Internal (not found)
-dialog.about.systeminfo.exiflib.external=External
-dialog.about.systeminfo.exiflib.external.failed=External (not found)
 dialog.about.yes=Yes
 dialog.about.no=No
 dialog.about.credits=Credits
@@ -485,7 +481,7 @@ dialog.checkversion.newversion1=A new version of GpsPrune is now available!  The
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=This new version was released on
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=To download the new version, go to http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=To download the new version, go to https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=You can use the following shortcut keys instead of using the mouse
 dialog.keys.keylist=<table><tr><td>Arrow keys</td><td>Pan map left right, up, down</td></tr><tr><td>Ctrl + left, right arrow</td><td>Select previous or next point</td></tr><tr><td>Ctrl + up, down arrow</td><td>Zoom in or out</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Select previous, next segment</td></tr><tr><td>Ctrl + Home, End</td><td>Select first, last point</td></tr><tr><td>Del</td><td>Delete current point</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -564,7 +560,17 @@ dialog.diskcache.deleteall=Delete all tiles
 dialog.diskcache.deleted=Deleted %d files from the cache
 dialog.deletefieldvalues.intro=Select the field to delete for the current range
 dialog.deletefieldvalues.nofields=There are no fields to delete for this range
-dialog.setlinewidth.text=Enter the thickness of lines to draw for the tracks (1-4)
+dialog.displaysettings.linewidth=Thickness of lines for the tracks (1-4)
+dialog.displaysettings.antialias=Use antialiasing
+dialog.displaysettings.waypointicons=Waypoint icons
+dialog.displaysettings.wpicon.default=Default
+dialog.displaysettings.wpicon.ringpt=Round marker
+dialog.displaysettings.wpicon.plectrum=Plectrum
+dialog.displaysettings.wpicon.ring=Ring
+dialog.displaysettings.wpicon.pin=Board pin
+dialog.displaysettings.size.small=Small
+dialog.displaysettings.size.medium=Medium
+dialog.displaysettings.size.large=Large
 dialog.downloadosm.desc=Confirm to download the raw OSM data for the specified area:
 dialog.searchwikipedianames.search=Search for:
 dialog.weather.location=Location
@@ -596,6 +602,12 @@ dialog.deletebydate.column.keep=Keep
 dialog.deletebydate.column.delete=Delete
 dialog.setaltitudetolerance.text.metres=Limit (in metres) below which small climbs and descents will be ignored
 dialog.setaltitudetolerance.text.feet=Limit (in feet) below which small climbs and descents will be ignored
+dialog.settimezone.intro=Here you can select the timezone in which to display point timestamps
+dialog.settimezone.system=Use system timezone
+dialog.settimezone.custom=Use the following timezone:
+dialog.settimezone.list.toomany=Too many to choose
+dialog.settimezone.selectedzone=Selected timezone
+dialog.settimezone.offsetfromutc=Offset from UTC
 dialog.autoplay.duration=Duration (secs)
 dialog.autoplay.usetimestamps=Use point timestamps
 dialog.autoplay.rewind=Back to beginning
@@ -812,7 +824,6 @@ wikipedia.lang=en
 openweathermap.lang=en
 webservice.peakfinder=Open Peakfinder.org
 webservice.geohack=Open Geohack page
-webservice.panoramio=Open Panoramio map
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -870,7 +881,6 @@ error.jpegload.dialogtitle=Error loading photos
 error.jpegload.nofilesfound=No files found
 error.jpegload.nojpegsfound=No jpeg files found
 error.jpegload.nogpsfound=No GPS information found
-error.jpegload.exifreadfailed=Failed to read Exif information. No Exif information can be read\nwithout either an internal or external library.
 error.audioload.nofilesfound=No audio clips found
 error.gpsload.unknown=Unknown error
 error.undofailed.title=Undo failed
index 53554ddaf1d32821672115a208ab54dc0c081990..df1bc280301c56b134f63bd8e36243d3169285cd 100644 (file)
@@ -38,7 +38,7 @@ menu.view.browser.yahoo=Mapas Yahoo
 menu.view.browser.bing=Mapas Bing
 menu.settings=Preferencias
 menu.settings.onlinemode=Cargar mapas de Internet
-menu.settings.antialias=Usar antialiasing
+dialog.displaysettings.antialias=Usar antialiasing
 menu.settings.autosave=Auto Guardar
 menu.help=Ayuda
 
@@ -83,7 +83,6 @@ function.sendtogps=Enviar datos al GPS
 function.exportkml=Exportar KML
 function.exportgpx=Exportar GPX
 function.exportpov=Exportar POV
-function.exportsvg=Exportar SVG
 function.exportimage=Exportar imagen
 function.editwaypointname=Editar nombre de waypoint
 function.compress=Comprimir track
@@ -122,7 +121,6 @@ function.mapillary=Buscar en Mapillary
 function.downloadosm=Descargar datos OSM del \u00e1rea
 function.duplicatepoint=Duplicar punto
 function.setcolours=Establecer color
-function.setlinewidth=Establecer ancho de l\u00ednea
 function.setlanguage=Establecer lenguaje
 function.connecttopoint=Conectar con punto
 function.disconnectfrompoint=Desconectar de punto
@@ -175,8 +173,9 @@ dialog.openoptions.deliminfo.norecords=Ningun dato
 dialog.openoptions.altitudeunits=Unidades altitud
 dialog.openoptions.speedunits=Unidades velocidad
 dialog.openoptions.vertspeedunits=Unidades velocidad vertical
-dialog.openoptions.vspeed.positiveup=Velocidades positivos significan hacia arriba
-dialog.openoptions.vspeed.positivedown=Velocidades positivos significan hacia abajo
+dialog.openoptions.vspeed.intro=Velocidades positivos significan:
+dialog.openoptions.vspeed.positiveup=hacia arriba
+dialog.openoptions.vspeed.positivedown=hacia abajo
 dialog.open.contentsdoubled=Este archivo contiene dos copias de cada punto,\nuna como "waypoints" y otra como puntos de recorrido.
 dialog.selecttracks.intro=Seleccionar recorrido/s a cargar
 dialog.selecttracks.noname=Innominados
@@ -269,10 +268,6 @@ dialog.baseimage.zoom=Zoom
 dialog.baseimage.incomplete=Imagen incompleta
 dialog.baseimage.tiles=Recuadros
 dialog.baseimage.size=Tama\u00f1o de la imagen
-dialog.exportsvg.text=Seleccione los par\u00e1metros para exportar a SVG
-dialog.exportsvg.phi=\u00c1ngulo de azimuth \u03d5
-dialog.exportsvg.theta=\u00c1ngulo de elevaci\u00f3n
-dialog.exportsvg.gradients=Usar degradado para sombras
 dialog.exportimage.drawtrack=Dibujar track
 dialog.exportimage.drawtrackpoints=Dibujar puntos del track
 dialog.exportimage.textscalepercent=Agrandamiento del texto (%)
@@ -388,6 +383,9 @@ dialog.wikipedia.column.name=Nombre del art\u00edculo
 dialog.wikipedia.column.distance=Distancia
 dialog.wikipedia.nonefound=No se encontraron puntos
 dialog.wikipedia.gallery=Galeria
+dialog.osmpois.column.name=Nombre
+dialog.osmpois.column.type=Tipo
+dialog.osmpois.nonefound=No se encontraron puntos
 dialog.geocaching.nonefound=No se encontraron tesoros
 dialog.correlate.notimestamps=No hay informaci\u00f3n de tiempo para los puntos, as\u00ed que no hay nada que correlacionar con las fotos.
 dialog.correlate.nouncorrelatedphotos=No hay fotos no correlacionadas.\n\u00bfEst\u00e1 seguro de que desea continuar?
@@ -447,12 +445,12 @@ dialog.deletemarked.nonefound=Ning\u00fan punto eliminado
 dialog.pastecoordinates.desc=Ingresar o pegar las coordenadas aqu\u00ed
 dialog.pastecoordinates.coords=Coordenadas
 dialog.pastecoordinates.nothingfound=Por favor verificar las coordenadas e intentar nuevamente
-dialog.help.help=Por favor, ver\n http://gpsprune.activityworkshop.net/\npara m\u00e1s informaci\u00f3n y gu\u00edas del usuario.
+dialog.help.help=Por favor, ver\n https://gpsprune.activityworkshop.net/\npara m\u00e1s informaci\u00f3n y gu\u00edas del usuario.
 dialog.about.version=Versi\u00f3n
 dialog.about.build=Construcci\u00f3n
 dialog.about.summarytext1=GpsPrune es un programa para cargar, mostrar y editar datos de receptores GPS.
 dialog.about.summarytext2=Distribuido bajo el GNU GPL para uso libre y gratuito.<br>Se permite (y se anima) la copia, redistribuci\u00f3n y modificaci\u00f3n de acuerdo<br>a las condiciones incluidas en el archivo <code>licence.txt</code>.
-dialog.about.summarytext3=Por favor, ver <code style="font-weight:bold">http://activityworkshop.net/</code> para m\u00e1s informaci\u00f3n y gu\u00edas del usuario.
+dialog.about.summarytext3=Por favor, ver <code style="font-weight:bold">https://activityworkshop.net/</code> para m\u00e1s informaci\u00f3n y gu\u00edas del usuario.
 dialog.about.languages=Idiomas disponibles
 dialog.about.translatedby=Traducci\u00f3n al espa\u00f1ol realizada por Miguel, In\u00e9s, josatoc y Javier
 dialog.about.systeminfo=Informacion del sistema
@@ -463,11 +461,6 @@ dialog.about.systeminfo.povray=Povray instalado
 dialog.about.systeminfo.exiftool=Exiftool instalado
 dialog.about.systeminfo.gpsbabel=Gpsbabel instalado
 dialog.about.systeminfo.gnuplot=Gnuplot instalado
-dialog.about.systeminfo.exiflib=Biblioteca exif
-dialog.about.systeminfo.exiflib.internal=Interna
-dialog.about.systeminfo.exiflib.internal.failed=Interna (no encontrada)
-dialog.about.systeminfo.exiflib.external=Externa
-dialog.about.systeminfo.exiflib.external.failed=Externa (no encontrada)
 dialog.about.yes=Si
 dialog.about.no=No
 dialog.about.credits=Cr\u00e9ditos
@@ -486,7 +479,7 @@ dialog.checkversion.newversion1=\u00a1Una nueva versi\u00f3n de GpsPrune est\u00
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=La nueva versi\u00f3n fue lanzada en
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Para descargar la nueva versi\u00f3n visite http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Para descargar la nueva versi\u00f3n visite https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Usted puede usar el siguiente atajo en lugar de usar el rat\u00f3n
 dialog.keys.keylist=<table><tr><td>Teclas de cursor</td><td>Desplazar a la izquierde, derecha, arriba, abajo</td></tr><tr><td>Ctrl + cursor izquierda, derecha</td><td>Seleccionar punto siguiente o anterior</td></tr><tr><td>Ctrl + cursor arriba, abajo</td><td>Ampliar o reducir zoom</td></tr><tr><td>Ctrl + Av Pag, Re Pag</td><td>Seleccionar segmento siguiente, anterior</td></tr><tr><td>Ctrl + Inicio, Fin</td><td>Seleccionar primer, \u00faltimo punto</td></tr><tr><td>Supr</td><td>Eliminar punto actual</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -565,7 +558,13 @@ dialog.diskcache.deleteall=Borrar todos los recuadros
 dialog.diskcache.deleted=Borrado %d archivos del cache
 dialog.deletefieldvalues.intro=Seleccionar el campo a eliminar para el rango actual
 dialog.deletefieldvalues.nofields=No hay campos a eliminar para el rango actual
-dialog.setlinewidth.text=Introduzca la anchura de las l\u00edneas a dibujar para los recorridos (1-4)
+dialog.displaysettings.linewidth=Anchura de las l\u00edneas para los recorridos (1-4)
+dialog.displaysettings.waypointicons=Iconos de los waypoints
+dialog.displaysettings.wpicon.plectrum=Plectro
+dialog.displaysettings.wpicon.ring=Anillo
+dialog.displaysettings.size.small=Peque\u00f1o
+dialog.displaysettings.size.medium=Mediano
+dialog.displaysettings.size.large=Grande
 dialog.downloadosm.desc=Confirmar la descarga de datos en bruto de OSM para el \u00e1rea especificada.
 dialog.searchwikipedianames.search=Buscar:
 dialog.weather.location=Localidad
@@ -594,6 +593,7 @@ dialog.deletebydate.column.keep=Mantener
 dialog.deletebydate.column.delete=Eliminar
 dialog.setaltitudetolerance.text.metres=Limite (en metros) por debajo de cual peque\u00f1as subidas o bajadas ser\u00e1n ignoradas
 dialog.setaltitudetolerance.text.feet=Limite (en pies) por debajo de cual peque\u00f1as subidas o bajadas ser\u00e1n ignoradas
+dialog.settimezone.system=Zona horaria del sistema
 dialog.autoplay.duration=Duraci\u00f3n (seg)
 dialog.autoplay.usetimestamps=Usar informaci\u00f3n de tiempo
 dialog.autoplay.rewind=Rebobinar
@@ -808,7 +808,6 @@ wikipedia.lang=es
 openweathermap.lang=sp
 webservice.peakfinder=Abrir Peakfinder.org
 webservice.geohack=Abrir Geohack
-webservice.panoramio=Abrir mapa Panoramio
 
 ## Cardinals for 3d plots
 cardinal.n=N
@@ -866,7 +865,6 @@ error.jpegload.dialogtitle=Error cargando fotos
 error.jpegload.nofilesfound=No se encuentra ning\u00fan archivo
 error.jpegload.nojpegsfound=No se encuentra ning\u00fan archivo jpeg
 error.jpegload.nogpsfound=No se encuentra informaci\u00f3n GPS
-error.jpegload.exifreadfailed=Fallo al leer la informaci\u00f3n Exif. No se puede leer ninguna informaci\u00f3n Exif\ncon las librer\u00edas internas ni externas.
 error.audioload.nofilesfound=No se encontraron archivos de audio
 error.gpsload.unknown=Error desconocido
 error.undofailed.title=Fallo al deshacer
diff --git a/tim/prune/lang/prune-texts_fi.properties b/tim/prune/lang/prune-texts_fi.properties
new file mode 100644 (file)
index 0000000..76cdcde
--- /dev/null
@@ -0,0 +1,909 @@
+# Text entries for the GpsPrune application
+# Finnish entries thanks to Erkki Argillander
+
+# Menu entries
+menu.file=Tiedosto
+menu.file.addphotos=Lis\u00e4\u00e4 kuvat...
+menu.file.recentfiles=Avaa viimeaikainen
+menu.file.save=Tallenna tekstin\u00e4...
+menu.file.exit=Lopeta
+menu.online=Online
+menu.track=Reitti
+menu.track.undo=Peru
+menu.track.clearundo=Tyhjenn\u00e4 perumislista
+menu.track.markrectangle=Merkkaa pisteet suunnikkaasta...
+menu.range=Pistealue
+menu.range.all=Valitse kaikki
+menu.range.none=Valitse 'ei mit\u00e4\u00e4n'
+menu.range.start=Aseta alkupiste
+menu.range.end=Aseta loppupiste
+menu.range.average=Keskiarvovalinta
+menu.range.reverse=K\u00e4\u00e4nteinen pistealue
+menu.range.mergetracksegments=Yhdist\u00e4 reittilohkot
+menu.range.cutandmove=Leikkaa ja siirr\u00e4 valitut
+menu.point=Piste
+menu.point.editpoint=Muokkaa pistett\u00e4
+menu.point.deletepoint=Poista piste
+menu.photo=Kuva
+menu.photo.saveexif=Talleta Exif-tietoihin
+menu.audio=\u00c4\u00e4net
+menu.view=N\u00e4kym\u00e4
+menu.view.showsidebars=N\u00e4yt\u00e4 sivupalkit
+menu.view.browser=Selainikkunan karttal\u00e4hde
+menu.view.browser.google=Google
+menu.view.browser.openstreetmap=Openstreetmap
+menu.view.browser.mapquest=Mapquest
+menu.view.browser.yahoo=Yahoo
+menu.view.browser.bing=Bing
+menu.settings=Asetukset
+menu.settings.onlinemode=Lataa kartat Internetist\u00e4
+menu.settings.autosave=Tallenna asetukset automaattisesti lopetettaessa
+menu.help=Ohje
+# Popup menu for map
+menu.map.zoomin=L\u00e4henn\u00e4
+menu.map.zoomout=Loitonna
+menu.map.zoomfull=Sovita selainikkunaan
+menu.map.newpoint=Luo uusi reittipiste
+menu.map.drawpoints=Luo reittipisteiden sarja
+menu.map.connect=Yhdist\u00e4 reittipisteet
+menu.map.autopan=Autopan
+menu.map.showmap=N\u00e4yt\u00e4 kartta
+menu.map.showscalebar=N\u00e4yt\u00e4 mittakaava-asteikko
+menu.map.editmode=Muokkaustila
+
+# Alt keys for menus
+altkey.menu.file=T
+altkey.menu.online=O
+altkey.menu.range=A
+altkey.menu.track=R
+altkey.menu.point=P
+altkey.menu.view=N
+altkey.menu.photo=K
+altkey.menu.audio=E
+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=Avaa...
+function.importwithgpsbabel=Tuo tiedosto GPSBabel'lla...
+function.loadfromgps=Lataa data GPS:st\u00e4...
+function.sendtogps=L\u00e4het\u00e4 data GPS:\u00e4\u00e4n...
+function.exportkml=Vie KML
+function.exportgpx=Vie GPX
+function.exportpov=Vie POV
+function.exportimage=Vie kuva
+function.editwaypointname=Muokkaa kohdepisteen nime\u00e4
+function.compress=Tiivist\u00e4 reitti...
+function.marklifts=Merkkaa yl\u00e4m\u00e4kinousut
+function.deleterange=Poista pistealue
+function.deletemarked=Poista merkityt pisteet
+function.croptrack=Typist\u00e4 reitti alueseen
+function.interpolate=Interpoloi pisteet
+function.deletebydate=Poista pisteet p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4n mukaan
+function.addtimeoffset=Lis\u00e4\u00e4 aikapoikkeama
+function.addaltitudeoffset=Lis\u00e4\u00e4 korkeuspoikkeama
+function.findwaypoint=Etsi kohdepiste
+function.rearrangewaypoints=J\u00e4rjest\u00e4 kohdepisteet
+function.convertnamestotimes=Muunna kohdepisteiden nimet ajoiksi
+function.deletefieldvalues=Poista kenttien arvoja...
+function.pastecoordinates=Anna uudet koordinaatit...
+function.charts=Kaaviot
+function.show3d=3D-n\u00e4kym\u00e4
+function.distances=V\u00e4limatkat
+function.fullrangedetails=Koko pistealueen tiedot
+function.estimatetime=Arvioi aika
+function.learnestimationparams=Opi aika-arvion parametrit
+function.autoplay=Animoi reitti
+function.selectsegment=Valitse nykyinen lohko
+function.splitsegments=Pilko reitti lohkoihin
+function.sewsegments=Yhdist\u00e4 reittilohkot
+function.createmarkerwaypoints=Luo merkityt kohdepisteet
+function.getgpsies=Lataa reitit Gpsies.com:sta
+function.uploadgpsies=Vie reitti Gpsies.com:iin...
+function.lookupsrtm=Lue korkeysk\u00e4yr\u00e4t SRTM:st\u00e4
+function.downloadsrtm=Lataa SRTM-palat
+function.getwikipedia=Hae likeiset Wikipedia-artikkelit
+function.searchwikipedianames=Etsi nimell\u00e4 Wikipedia:sta...
+function.searchosmpois=Etsi l\u00e4heiset OSM-pisteet
+function.searchopencachingde=Etsi OpenCaching.de:sta...
+function.mapillary=Etsi kuvia Mapillary:sta...
+function.downloadosm=Lataa alueen OSM-data...
+function.duplicatepoint=Kahdenna piste
+function.connecttopoint=Liit\u00e4 pisteeseen
+function.disconnectfrompoint=Irroita pisteest\u00e4
+function.removephoto=Poista kuva
+function.correlatephotos=Korreloi kuvat
+function.rearrangephotos=J\u00e4rjest\u00e4 kuvat...
+function.rotatephotoleft=Kierr\u00e4 kuvaa vasemmalle
+function.rotatephotoright=Kierr\u00e4 kuvaa oikealle
+function.photopopup=N\u00e4yt\u00e4 kuvaikkunassa
+function.ignoreexifthumb=Ignore exif thumbnail
+function.loadaudio=Lis\u00e4\u00e4 \u00e4\u00e4nileikkeet...
+function.removeaudio=Poista \u00e4\u00e4nileike
+function.correlateaudios=Korreloi \u00e4\u00e4net
+function.playaudio=Toista \u00e4\u00e4nileike
+function.stopaudio=Pys\u00e4yt\u00e4 \u00e4\u00e4nileike
+function.setmapbg=Aseta taustakartan l\u00e4hde...
+function.setpaths=Aseta ohjelmien polut...
+function.setcolours=Aseta v\u00e4rit...
+function.setdisplaysettings=Aseta n\u00e4kym\u00e4n asetukset
+function.setlanguage=Aseta kieli...
+function.help=Ohje
+function.showkeys=N\u00e4yt\u00e4 n\u00e4pp\u00e4inkomennot
+function.about=Tietoja GpsPrune:sta
+function.checkversion=Tarkista uusi versio
+function.saveconfig=Talleta asetukset
+function.diskcache=Talleta kartat hakemistoon...
+function.managetilecache=Hallitse karttapalojen v\u00e4limuistia
+function.getweatherforecast=Hae s\u00e4\u00e4ennuste..
+function.setaltitudetolerance=Aseta korkeuden herkkyys...
+function.selecttimezone=Aseta aikavy\u00f6hyke
+
+# Dialogs
+dialog.exit.confirm.title=Lopeta GpsPrune
+dialog.exit.confirm.text=Muokkaamasi data ei ole tallennettu.\nHaluatko varmasti lopettaa?
+dialog.openappend.title=Lis\u00e4\u00e4 nykyiseen dataan
+dialog.openappend.text=Lis\u00e4t\u00e4\u00e4nk\u00f6 t\u00e4m\u00e4 data jo ladattuun dataan?
+dialog.deletepoint.title=Poista reittipiste
+dialog.deletepoint.deletephoto=Poistetaanko yhteys pisteeseen liitettyyn kuvaan?
+dialog.deletephoto.title=Poista kuva
+dialog.deletephoto.deletepoint=Poistetaanko t\u00e4h\u00e4n kuvaan liitetty piste?
+dialog.deleteaudio.deletepoint=Poistetaanko t\u00e4h\u00e4n \u00e4\u00e4nileikkeeseen liitetty piste?
+dialog.openoptions.title=Avauksen valinnat
+dialog.openoptions.filesnippet=Purettava tiedosto
+dialog.load.table.field=Kentt\u00e4
+dialog.load.table.datatype=Datan tyyppi
+dialog.load.table.description=Kuvaus
+dialog.delimiter.label=Kenttien erotin
+dialog.delimiter.comma=Pilkku ,
+dialog.delimiter.tab=Tabulaattori
+dialog.delimiter.space=V\u00e4lily\u00f6nti
+dialog.delimiter.semicolon=Puolipiste ;
+dialog.delimiter.other=Muu
+dialog.openoptions.deliminfo.records=tietueet, joissa
+dialog.openoptions.deliminfo.fields=kent\u00e4t
+dialog.openoptions.deliminfo.norecords=Ei tietueita
+dialog.openoptions.altitudeunits=Korkeuden yksik\u00f6t
+dialog.openoptions.speedunits=Nopeuden yksik\u00f6t
+dialog.openoptions.vertspeedunits=Pystysuuntaisen nopeuden yksik\u00f6t
+dialog.openoptions.vspeed.positiveup=Positiiviset nopeudet yl\u00f6sp\u00e4in
+dialog.openoptions.vspeed.positivedown=Positiiviset nopeudet alap\u00e4in
+dialog.open.contentsdoubled=T\u00e4ss\u00e4 tiedostossa on kaksi kopioita kustakin pisteest\u00e4,\ntoinen kohde- ja toinen reittipisteen\u00e4.
+dialog.selecttracks.intro=Valitse ladattava reitti tai reitit
+dialog.selecttracks.noname=Nimet\u00f6n
+dialog.jpegload.subdirectories=Sis\u00e4llyt\u00e4 alihakemistot
+dialog.jpegload.loadjpegswithoutcoords=Sis\u00e4llyt\u00e4 koordinaatittomat kuvat
+dialog.jpegload.loadjpegsoutsidearea=Sis\u00e4llyt\u00e4 kuvat nykyisen kartta-alueen ulkopuolelta
+dialog.jpegload.progress.title=Ladataan kuvia
+dialog.jpegload.progress=Odota, kun kuvia etsit\u00e4\u00e4n
+dialog.gpsload.nogpsbabel=gpsbabel-ohjelmaa ei l\u00f6ytynyt. Jatketaanko?
+dialog.gpsload.device=Laittee nimi (esim. usb:)
+dialog.gpsload.format=Muoto
+dialog.gpsload.getwaypoints=Lataa kohdepisteet
+dialog.gpsload.gettracks=Lataa reitit
+dialog.gpsload.save=Tallenna tiedostoon
+dialog.gpssend.sendwaypoints=L\u00e4het\u00e4 kohdepisteet
+dialog.gpssend.sendtracks=L\u00e4het\u00e4 reitit
+dialog.gpssend.trackname=Reitin nimi
+dialog.gpsbabel.filters=Suodattimet
+dialog.addfilter.title=Lis\u00e4\u00e4 suodatin
+dialog.gpsbabel.filter.discard=Hylk\u00e4\u00e4
+dialog.gpsbabel.filter.simplify=Yksinkertaista
+dialog.gpsbabel.filter.distance=V\u00e4limatka
+dialog.gpsbabel.filter.interpolate=Interpoloi
+dialog.gpsbabel.filter.discard.intro=Hylk\u00e4\u00e4 pisteet, jos
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
+dialog.gpsbabel.filter.discard.numsats=Sateliittien lukum\u00e4\u00e4r\u00e4 <
+dialog.gpsbabel.filter.discard.nofix=Point has no fix
+dialog.gpsbabel.filter.discard.unknownfix=Point has unknown fix
+dialog.gpsbabel.filter.simplify.intro=Poista pisteit\u00e4, kunnes
+dialog.gpsbabel.filter.simplify.maxpoints=Pisteiden m\u00e4\u00e4r\u00e4 <
+dialog.gpsbabel.filter.simplify.maxerror=tai virhev\u00e4limatka <
+dialog.gpsbabel.filter.simplify.crosstrack=cross-track
+dialog.gpsbabel.filter.simplify.length=pituusero
+dialog.gpsbabel.filter.simplify.relative=suhteessa hdop:iin
+dialog.gpsbabel.filter.distance.intro=Poista sellaiset pisteet, jotka l\u00e4hell\u00e4 jotain edellist\u00e4 pistett\u00e4
+dialog.gpsbabel.filter.distance.distance=Jos v\u00e4limatka <
+dialog.gpsbabel.filter.distance.time=ja aikaero <
+dialog.gpsbabel.filter.interpolate.intro=Lis\u00e4\u00e4 lis\u00e4pisteit\u00e4 reittipisteiden v\u00e4liin
+dialog.gpsbabel.filter.interpolate.distance=Jos v\u00e4limatka >
+dialog.gpsbabel.filter.interpolate.time=tai aikaero >
+dialog.saveoptions.title=Tallenna tiedosto
+dialog.save.fieldstosave=Tallennettavat kent\u00e4t
+dialog.save.table.field=Kent\u00e4
+dialog.save.table.hasdata=On dataa
+dialog.save.table.save=Tallenna
+dialog.save.headerrow=Tulosta otsikkorivi
+dialog.save.coordinateunits=Koordinaattimuoto
+dialog.save.altitudeunits=Korkeuden yksikk\u00f6
+dialog.save.timestampformat=Aikaleiman muoto
+dialog.save.overwrite.title=Tiedosto jo olemassa
+dialog.save.overwrite.text=T\u00e4m\u00e4 tiedosto on jo olemassa. Haluatko varmasti tallentaa sen p\u00e4\u00e4lle?
+dialog.save.notypesselected=Pistetyyppej\u00e4 ei valittuna
+dialog.exportkml.text=Otsikko datalle
+dialog.exportkml.altitude=Absoluuttiset korkeudet (ilmailulle)
+dialog.exportkml.kmz=Tiivist\u00e4 tehd\u00e4ksesi 'kmz'-tiedosto
+dialog.exportkml.exportimages=Vie esikatselukuvat 'kmz'-tiedostoon
+dialog.exportkml.imagesize=Kuvakoko
+dialog.exportkml.trackcolour=Reitin v\u00e4ri
+dialog.exportkml.standardkml=Vakio KML
+dialog.exportkml.extendedkml=Laajennettu KML aikaleimoin
+dialog.exportgpx.name=Nimi
+dialog.exportgpx.desc=Kuvaus
+dialog.exportgpx.includetimestamps=Sis\u00e4llyt\u00e4 aikaleimat
+dialog.exportgpx.copysource=Kopioi l\u00e4hde-XML
+dialog.exportgpx.encoding=Koodaus
+dialog.exportgpx.encoding.system=J\u00e4rjestelm\u00e4
+dialog.exportgpx.encoding.utf8=UTF-8
+dialog.exportpov.text=Anna parametrit POV-vientiin
+dialog.exportpov.font=Kirjasintyyppi
+dialog.exportpov.camerax=Kamera X
+dialog.exportpov.cameray=Kamera Y
+dialog.exportpov.cameraz=Kamera Z
+dialog.exportpov.modelstyle=Mallin tyyli
+dialog.exportpov.ballsandsticks=Pallot ja kepit
+dialog.exportpov.tubesandwalls=Putket ja sein\u00e4t
+dialog.3d.warningtracksize=T\u00e4ss\u00e4 reitiss\u00e4 on paljon sellaisia reittipisteit\u00e4, joita Java3D ei mahdollisesti kykene n\u00e4ytt\u00e4m\u00e4\u00e4n.\nHaluatko varmasti jatkaa?
+dialog.3d.useterrain=N\u00e4yt\u00e4 maasto
+dialog.3d.terraingridsize=Ruudukon koko
+dialog.exportpov.baseimage=Peruskuva
+dialog.exportpov.cannotmakebaseimage=Peruskuvaa ei voi kirjoittaa
+dialog.baseimage.title=Karttakuva
+dialog.baseimage.useimage=K\u00e4yt\u00e4 karttakuvaa
+dialog.baseimage.mapsource=Karttal\u00e4hde
+dialog.baseimage.zoom=Suurennostaso
+dialog.baseimage.incomplete=Karttakuva ep\u00e4t\u00e4ydellinen
+dialog.baseimage.tiles=Karttapalat
+dialog.baseimage.size=Kuvan koko
+dialog.exportimage.noimagepossible=Karttakuvat pit\u00e4\u00e4 tallentaa levylle v\u00e4limuistiin, jotta niit\u00e4 voi k\u00e4ytt\u00e4\u00e4 vientiin.
+dialog.exportimage.drawtrack=Piirr\u00e4 reitti kartalle
+dialog.exportimage.drawtrackpoints=Piirr\u00e4 reittipisteit\u00e4
+dialog.exportimage.textscalepercent=Tekstin skaalauskerroin (%)
+dialog.pointtype.desc=Tallenna seuraavan tyyppiset pisteet:
+dialog.pointtype.track=Reittipisteet
+dialog.pointtype.waypoint=Kohdepisteet
+dialog.pointtype.photo=Kuvien pisteet
+dialog.pointtype.audio=\u00c4\u00e4nien pisteet
+dialog.pointtype.selection=Just selection
+dialog.confirmreversetrack.title=Vahvista k\u00e4\u00e4nteisj\u00e4rjest\u00e4minen
+dialog.confirmreversetrack.text=T\u00e4m\u00e4 reitti sis\u00e4lt\u00e4\u00e4 aikaleimoja, joiden aikaj\u00e4rjestys ei ole oikea pisteiden k\u00e4\u00e4teisj\u00e4rjestelyn j\u00e4lkeen.\nHaluatko varmasti k\u00e4\u00e4nteisj\u00e4rjest\u00e4\u00e4 t\u00e4m\u00e4n alueen?
+dialog.confirmcutandmove.title=Vahvista leikkaa ja siirr\u00e4
+dialog.confirmcutandmove.text=T\u00e4ss\u00e4 reitti sis\u00e4lt\u00e4\u00e4 aikaleimoja, jotka eiv\u00e4t ole j\u00e4rjestyksee\u00e4 siirron j\u00e4lkeen.\nHaluatko varmasti siirt\u00e4\u00e4 t\u00e4m\u00e4n pistelohkon?
+dialog.interpolate.parameter.text=Kunkin pisteparin v\u00e4liin luotavien pisteiden m\u00e4\u00e4r\u00e4
+dialog.interpolate.betweenwaypoints=Interpoloi kohdepisteiden v\u00e4linen reitti
+dialog.undo.title=Peruttavat toiminnot
+dialog.undo.pretext=Valitse yksi tai useampi peruttava toiminto
+dialog.undo.none.title=Ei voi perua
+dialog.undo.none.text=Ei peruttavia toimintoja!
+dialog.clearundo.title=Tyhjenn\u00e4 perumislista
+dialog.clearundo.text=Haluatko varmasti tyhjenttiedostottiedostot perumislistan?\nKaikki perumistiedot menetet\u00e4\u00e4n lopullisesti!
+dialog.pointedit.title=Muokkaa pistett\u00e4
+dialog.pointedit.intro=Valitse kukin kentt\u00e4 vuorollaan\nn\u00e4hd\u00e4ksesi ja muuttaaksesi sen arvoa
+dialog.pointedit.table.field=Kentt\u00e4
+dialog.pointedit.nofield=Kentt\u00e4\u00e4 ei valittuna
+dialog.pointedit.table.value=Arvo
+dialog.pointnameedit.name=Kohdepisteen nimi
+dialog.pointnameedit.uppercase=Suuraakkoset
+dialog.pointnameedit.lowercase=Pienaakkoset
+dialog.pointnameedit.titlecase=Iso alkukirjain
+dialog.addtimeoffset.add=Lis\u00e4tt\u00e4v\u00e4 aika
+dialog.addtimeoffset.subtract=V\u00e4hennett\u00e4v\u00e4 aika
+dialog.addtimeoffset.days=P\u00e4iv\u00e4t
+dialog.addtimeoffset.hours=Tunnit
+dialog.addtimeoffset.minutes=Minuutit
+dialog.addtimeoffset.notimestamps=Aikapoikkeamaa ei voi lis\u00e4t\u00e4 t\u00e4st\u00e4 valinnasta puuttuvan aikaleiman vuoksi
+dialog.findwaypoint.intro=Anna kohdepisteen nimen osa
+dialog.findwaypoint.search=Etsi
+dialog.saveexif.title=Tallenna Exif
+dialog.saveexif.intro=Valitse tallennettavat kuvat ruksaamalla valintaruutuihin
+dialog.saveexif.nothingtosave=Koordinaattitieto muuttumaton -  ei uutta tallennettavaa
+dialog.saveexif.noexiftool='exiftool'-ohjelmaa ei l\u00f6ytynyt. Jatketaanko?
+dialog.saveexif.table.photoname=Kuvan nimi
+dialog.saveexif.table.status=Tila
+dialog.saveexif.table.save=Tallenna
+dialog.saveexif.photostatus.connected=Liitetty
+dialog.saveexif.photostatus.disconnected=Liitt\u00e4m\u00e4t\u00f6n
+dialog.saveexif.photostatus.modified=Muutettu
+dialog.saveexif.overwrite=Korvaa tiedostot
+dialog.saveexif.force=Pakota huomioimatta pikkuvirheit\u00e4
+dialog.charts.xaxis=X-akseli
+dialog.charts.yaxis=Y-akseli
+dialog.charts.output=Tulostus
+dialog.charts.screen=N\u00e4yt\u00f6lle
+dialog.charts.svg=SVG-tiedostoon
+dialog.charts.svgwidth=SVG:n leveys kuvapistein\u00e4
+dialog.charts.svgheight=SVG:n korkeus kuvapistein\u00e4
+dialog.charts.needaltitudeortimes=Reitiss\u00e4 pit\u00e4\u00e4 olla joko korkeustiedot tai aikaleimat kaaviokuvien luomiseksi
+dialog.charts.gnuplotnotfound='gnuplot'-ohjelmaa ei l\u00f6ytynyt annetusta hakemistopolusta
+dialog.distances.intro=Pisteiden v\u00e4limatkat linnuntiet\u00e4
+dialog.distances.column.from=Pisteest\u00e4
+dialog.distances.column.to=Pisteeseen
+dialog.distances.currentpoint=Nykypiste
+dialog.distances.toofewpoints=T\u00e4m\u00e4 toiminto edellytt\u00e4\u00e4 kohdepisteit\u00e4 niiden v\u00e4limatkojen laskemiseksi
+dialog.fullrangedetails.intro=T\u00e4ss\u00e4 tiedot valitulle alueelle
+dialog.fullrangedetails.coltotal=Reitti aukkoineen
+dialog.fullrangedetails.colsegments=Reitti aukoitta
+dialog.estimatetime.details=Tiedot
+dialog.estimatetime.gentle=Loiva
+dialog.estimatetime.steep=Jyrkk\u00e4
+dialog.estimatetime.climb=Nousu
+dialog.estimatetime.descent=Lasku
+dialog.estimatetime.parameters=Parametrit
+dialog.estimatetime.parameters.timefor=Aika matkalle
+dialog.estimatetime.results=Tulokset
+dialog.estimatetime.results.estimatedtime=Arvioitu aika
+dialog.estimatetime.results.actualtime=Todellinen aika
+dialog.estimatetime.error.nodistance=Aika-arviointi tarvitsee yhdistettyj\u00e4 reittipisteit\u00e4 v\u00e4limatkan laskemiseksi.
+dialog.estimatetime.error.noaltitudes=Valinta ei sis\u00e4ll\u00e4 yht\u00e4\u00e4n korkeustietoa
+dialog.learnestimationparams.intro=N\u00e4m\u00e4 ovat t\u00e4lle reitille lasketut parametrit
+dialog.learnestimationparams.averageerror=Keskim\u00e4\u00e4r\u00e4inen virhe
+dialog.learnestimationparams.combine=N\u00e4m\u00e4 parametrit voidaan yhdist\u00e4\u00e4 nykyisiin arvoihin
+dialog.learnestimationparams.combinedresults=Yhdistetyt tulokset
+dialog.learnestimationparams.weight.100pccurrent=Pid\u00e4 nykyiset arvot
+dialog.learnestimationparams.weight.current=nykyinen
+dialog.learnestimationparams.weight.calculated=laskettu
+dialog.learnestimationparams.weight.50pc=Nykyisten ja lasketttujen arvojen keskiarvo
+dialog.learnestimationparams.weight.100pccalculated=K\u00e4yt\u00e4 laskettuja arvoja
+dialog.setmapbg.intro=Valitse jokin annetuista karttal\u00e4hteist\u00e4\ntai lis\u00e4\u00e4 uusi
+dialog.addmapsource.title=Lis\u00e4\u00e4 uusi karttal\u00e4hde
+dialog.addmapsource.sourcename=L\u00e4hteen nimi
+dialog.addmapsource.layer1url=Ensim\u00e4isen tason URL
+dialog.addmapsource.layer2url=Toisen tason valinnainen URL
+dialog.addmapsource.maxzoom=Maksimisuurennus
+dialog.addmapsource.noname=Nimet\u00f6n
+dialog.gpsies.column.name=Reitin nimi
+dialog.gpsies.column.length=Pituus
+dialog.gpsies.description=Kuvaus
+dialog.gpsies.nodescription=Ei kuvausta
+dialog.gpsies.nonefound=Reittej\u00e4 ei l\u00f6ytynyt
+dialog.gpsies.username=Gpsies.com:n k\u00e4ytt\u00e4j\u00e4nimi
+dialog.gpsies.password=Gpsies.com:n salasana
+dialog.gpsies.keepprivate=Pid\u00e4 reitti yksityisen\u00e4
+dialog.gpsies.confirmopenpage=Open the web page for the uploaded track?
+dialog.gpsies.activities=Aktiviteettityypit
+dialog.gpsies.activity.trekking=Patikointi
+dialog.gpsies.activity.walking=K\u00e4vely
+dialog.gpsies.activity.jogging=Juoksu
+dialog.gpsies.activity.biking=Polkupy\u00f6r\u00e4ily
+dialog.gpsies.activity.motorbiking=Moottoripy\u00f6r\u00e4ily
+dialog.gpsies.activity.snowshoe=Lumikenk\u00e4ily
+dialog.gpsies.activity.sailing=Purjehdus
+dialog.gpsies.activity.skating=Luistelu
+dialog.mapillary.nonefound=Kuvia ei l\u00f6ytynyt
+dialog.wikipedia.column.name=Artikkelin nimi
+dialog.wikipedia.column.distance=V\u00e4limatka
+dialog.wikipedia.nonefound=Wikipedia-artikkeleita ei l\u00f6ytynyt
+dialog.wikipedia.gallery=Gallery
+dialog.osmpois.column.name=Nimi
+dialog.osmpois.column.type=Tyyppi
+dialog.osmpois.nonefound=Pisteit\u00e4 ei l\u00f6ytynyt
+dialog.geocaching.nonefound=Geok\u00e4tk\u00f6j\u00e4 ei l\u00f6ytynyt
+dialog.correlate.notimestamps=Reittipisteiss\u00e4 ei ole aikaleimoja, joten yht\u00e4\u00e4n pistett\u00e4 ei voi aikakorreloida kuvien kanssa.
+dialog.correlate.nouncorrelatedphotos=Ei korreloimattomia kuvia.\nHaluatko varmasti jatkaa?
+dialog.correlate.nouncorrelatedaudios=Ei korreloimattomia \u00e4\u00e4hileikkeit\u00e4.\nHaluatko varmasti jatkaa?
+dialog.correlate.photoselect.intro=Valitse yksi n\u00e4ist\u00e4 korreloiduista kuvista aikapoikkeaman m\u00e4\u00e4ritt\u00e4miseksi
+dialog.correlate.select.photoname=Kuvan nimi
+dialog.correlate.select.timediff=Aikaero
+dialog.correlate.select.photolater=Kuva my\u00f6hemmin
+dialog.correlate.options.intro=Valitse vaihtoehtoiset arvot automaattiseen korrelointiin
+dialog.correlate.options.offsetpanel=Aikapoikkeama
+dialog.correlate.options.offset=Poikkeama
+dialog.correlate.options.offset.hours=tunnit,
+dialog.correlate.options.offset.minutes=minuutit ja
+dialog.correlate.options.offset.seconds=sekunnit
+dialog.correlate.options.photolater=Kuva pistett\u00e4 my\u00f6hemmin
+dialog.correlate.options.pointlaterphoto=Piste kuvaa my\u00f6hemmin
+dialog.correlate.options.audiolater=\u00c4\u00e4nileike pistett\u00e4 my\u00f6hemmin
+dialog.correlate.options.pointlateraudio=Piste \u00e4\u00e4nileikett\u00e4 my\u00f6hemmin
+dialog.correlate.options.limitspanel=Korreloinnin raja-arvot
+dialog.correlate.options.notimelimit=Ei aikarajoja
+dialog.correlate.options.timelimit=Aikarajat
+dialog.correlate.options.nodistancelimit=Ei et\u00e4isyysrajoja
+dialog.correlate.options.distancelimit=Eta\u00e4isyysrajat
+dialog.correlate.options.correlate=Korreloi
+dialog.correlate.alloutsiderange=Kaikki kohteet reitin aikarajojen ulkopuolella, joten mit\u00e4\u00e4n ei voi korreloida.\nKoita muuttaa poikkeamaa tai korreloida manuaalisesti ainakin yksi kohde.
+dialog.correlate.filetimes=Tiedoston aikaleimat osoittavat:
+dialog.correlate.filetimes2=\u00e4\u00e4nileikeen (?)
+dialog.correlate.correltimes=Korrelointiin k\u00e4yt\u00e4:
+dialog.correlate.timestamp.beginning=Aloitusaika
+dialog.correlate.timestamp.middle=Keskiaika
+dialog.correlate.timestamp.end=Lopetusaika
+dialog.correlate.audioselect.intro=Valitse yksi n\u00e4ist\u00e4 korreloiduista \u00e4\u00e4nileikkeist\u00e4 aikapoikkeaman m\u00e4\u00e4ritt\u00e4miseksi
+dialog.correlate.select.audioname=\u00c4\u00e4nileikkeen nimi
+dialog.correlate.select.audiolater=\u00c4\u00e4nileikke my\u00f6hemmin
+dialog.rearrangewaypoints.desc=Valitse kohde ja kohdepisteiden lajittelus\u00e4\u00e4nt\u00f6
+dialog.rearrangephotos.desc=Valitse kohde ja kuvapisteiden lajittelus\u00e4\u00e4nt\u00f6
+dialog.rearrange.tostart=Siirry alkuun
+dialog.rearrange.toend=Siirry loppuun
+dialog.rearrange.tonearest=Kukin l\u00e4heisimp\u00e4\u00e4n reittipisteeseen
+dialog.rearrange.nosort=\u00c4l\u00e4 lajittele
+dialog.rearrange.sortbyfilename=Lajittele tiedostonimen suhteen
+dialog.rearrange.sortbyname=Lajittele nimen suhteen
+dialog.rearrange.sortbytime=Lajittele ajan suhteen
+dialog.compress.duplicates.title=Kahdennosten poisto
+dialog.compress.closepoints.title=Likipisteen poisto
+dialog.compress.closepoints.paramdesc=Aikaj\u00e4nten kerroin
+dialog.compress.wackypoints.title=Omituisten pisteiden poisto
+dialog.compress.wackypoints.paramdesc=V\u00e4limatkakerroin
+dialog.compress.singletons.title=Irtopisteiden poisto
+dialog.compress.singletons.paramdesc=V\u00e4limatkakerroin
+dialog.compress.douglaspeucker.title=Douglas-Peucker-tiivistys
+dialog.compress.douglaspeucker.paramdesc=Aikaj\u00e4nnekerroin
+dialog.compress.summarylabel=Poistettavat pisteet
+dialog.compress.confirm=%d pistett\u00e4 on merkitty.\nPoista merkityt pisteet nyt?
+dialog.compress.confirmnone=yht\u00e4\u00e4n pistett\u00e4 ei ole merkitty
+dialog.deletemarked.nonefound=Yht\u00e4\u00e4n datapistett\u00e4 ei voitu poistaa
+dialog.pastecoordinates.desc=Kirjoita tai liit\u00e4 koordinaatit t\u00e4h\u00e4n
+dialog.pastecoordinates.coords=Koordinaatit
+dialog.pastecoordinates.nothingfound=Tarkista koordinaatit ja yrit\u00e4 uudestaan
+dialog.help.help=Lis\u00e4tietoja ja vihjeit\u00e4 webbisivulta:\nhttps://gpsprune.activityworkshop.net/\n\nMyyt\u00e4v\u00e4n\u00e4 PDF:n\u00e4 k\u00e4ytt\u00f6ohjekirja.
+dialog.about.version=Versio
+dialog.about.build=Build
+dialog.about.summarytext1=GpsPrune on ohjelma, jolla ladataan, tarkastellaan ja muokataan GPS-tallentimista<br>luettua paikkatietoa.
+dialog.about.summarytext2=Ohjelma on julkaistu GNU GPL-lisenssill\u00e4 vapaana ja avoimena l\u00e4hdekoodina<br>maailmanlaajuiseen k\u00e4ytt\u00f6\u00f6n ja edelleen kehitt\u00e4miseen.<br>Kopiointi, jakelu ja muuttelu on sallittua ja kannustettavaa jakelun mukana<br>tulleen <code>license.txt</code>-tiedoston ehtojen mukaisesti.
+dialog.about.summarytext3=Webbisivulta <code style="font-weight:bold">https://activityworkshop.net/</code> saat lis\u00e4\u00e4 tietoja ja vinkkej\u00e4 sek\u00e4<br>voit ostaa k\u00e4ytt\u00f6oppaan PDF-julkaisuna.
+dialog.about.languages=Kielivaihtoehdot
+dialog.about.translatedby=Suomenkielelle k\u00e4\u00e4nt\u00e4nyt Erkki.
+dialog.about.systeminfo=J\u00e4rjestelm\u00e4tiedot
+dialog.about.systeminfo.os=K\u00e4ytt\u00f6j\u00e4rjestelm\u00e4
+dialog.about.systeminfo.java=Java versio
+dialog.about.systeminfo.java3d=Java3d asennettu
+dialog.about.systeminfo.povray=Povray asennettu
+dialog.about.systeminfo.exiftool=Exiftool asennettu
+dialog.about.systeminfo.gpsbabel=Gpsbabel asennettu
+dialog.about.systeminfo.gnuplot=Gnuplot asennettu
+dialog.about.yes=Kyll\u00e4
+dialog.about.no=Ei
+dialog.about.credits=Tekij\u00e4t
+dialog.about.credits.code=GpsPrune:n ohjelmoinut
+dialog.about.credits.exifcode=Exif:in koodannut
+dialog.about.credits.icons=Jotkut kuvakkeet t\u00e4\u00e4lt\u00e4
+dialog.about.credits.translators=K\u00e4\u00e4nt\u00e4j\u00e4t
+dialog.about.credits.translations=K\u00e4\u00e4nt\u00e4misess\u00e4 avustaneet
+dialog.about.credits.devtools=Kehitysty\u00f6kalut
+dialog.about.credits.othertools=Muut ty\u00f6kalut
+dialog.about.credits.thanks=Kiitokset
+dialog.about.readme=Lueminut
+dialog.checkversion.error=Versionumeroa ei voitu tarkistaa.\nTarkista verkkoyhteys.
+dialog.checkversion.uptodate=K\u00e4yt\u00f6ss\u00e4si on uusin GpsPrune:n versio.
+dialog.checkversion.newversion1=GpsPrune:sta on saatavissa uudempi versio! Uusin versio on nyt
+dialog.checkversion.newversion2=.
+dialog.checkversion.releasedate1=T\u00e4m\u00e4 uusin versio on julkaistu
+dialog.checkversion.releasedate2=.
+dialog.checkversion.download=Uusimman version voit ladata webbisivulta https://gpsprune.activityworkshop.net/download.html.
+dialog.keys.intro=Voit hiiren asemesta k\u00e4ytt\u00e4\u00e4 seuraavia n\u00e4pp\u00e4inkomentoja:
+dialog.keys.keylist=<table><tr><td>Nuolin\u00e4pp\u00e4imill\u00e4</td><td>voit siirt\u00e4\u00e4 kartaa vasemmalle, oikealle, yl\u00f6s ja alas</td></tr><tr><td>Ctrl + vasen/oikea nuoli\u00e4pp\u00e4in</td><td>Valitse edellinen/seuraava reittipiste</td></tr><tr><td>Ctrl + yl\u00f6s/alas nuolin\u00e4pp\u00e4in</td><td>L\u00e4henn\u00e4 tai loitonna</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Valitse edellinen/seuraava pistelohko</td></tr><tr><td>Ctrl + Home, End</td><td>Valitse ensimm\u00e4inen/viimeinen reittipiste</td></tr><tr><td>Del</td><td>Poista valittu reittipiste</td></tr></table>
+dialog.keys.normalmodifier=Ctrl
+dialog.keys.macmodifier=Command
+dialog.saveconfig.desc=Seuraava asetukset voidaan tallentaa asetustiedostoon:
+dialog.saveconfig.prune.trackdirectory=Reittihakemisto
+dialog.saveconfig.prune.photodirectory=Kuvahakemisto
+dialog.saveconfig.prune.languagecode=Kielitunnus (FI)
+dialog.saveconfig.prune.languagefile=Kielitiedosto
+dialog.saveconfig.prune.gpsdevice=GPS-laite
+dialog.saveconfig.prune.gpsformat=GPS-formaatti
+dialog.saveconfig.prune.povrayfont=Povray fontti
+dialog.saveconfig.prune.gnuplotpath=Polku ohjelmaan gnuplot
+dialog.saveconfig.prune.gpsbabelpath=Polku ohjelmaan gpsbabel
+dialog.saveconfig.prune.exiftoolpath=Polku ohjelmaan exiftool
+dialog.saveconfig.prune.mapsource=Valittu karttal\u00e4hde
+dialog.saveconfig.prune.mapsourcelist=Karttal\u00e4hteet
+dialog.saveconfig.prune.diskcache=Karttojen v\u00e4limuisti
+dialog.saveconfig.prune.kmzimagewidth=KMZ:in kuvan leveys
+dialog.saveconfig.prune.colourscheme=V\u00e4rij\u00e4rjestelm\u00e4
+dialog.saveconfig.prune.linewidth=Viivan leveys
+dialog.saveconfig.prune.kmltrackcolour=KML:n reitin v\u00e4ri
+dialog.saveconfig.prune.autosavesettings=Tallenna asetukset automaattisesti
+dialog.setpaths.intro=Halutessasi voit valita hakemistopolut ulkoisiin sovelluksiin:
+dialog.setpaths.found=Polku l\u00f6ydetty?
+dialog.addaltitude.noaltitudes=Valittu alue ei sis\u00e4ll\u00e4 korkeustietoja
+dialog.addaltitude.desc=Lis\u00e4tt\u00e4v\u00e4 korkeuspoikkeama
+dialog.lookupsrtm.overwritezeros=Korvataanko nolla-arvoiset korkeusarvot?
+dialog.setcolours.intro=Klikkaa v\u00e4rilaikua alla vaihtaaksesi kohteen v\u00e4ri\u00e4
+dialog.setcolours.background=Tausta
+dialog.setcolours.borders=Reunat
+dialog.setcolours.lines=Viivat
+dialog.setcolours.primary=Ensisijainen
+dialog.setcolours.secondary=Toissijainen
+dialog.setcolours.point=Reittipisteet
+dialog.setcolours.selection=Valinta
+dialog.setcolours.text=Teksti
+dialog.colourchooser.title=Valitse v\u00e4ri
+dialog.colourchooser.red=Punainen (R)
+dialog.colourchooser.green=Vihre\u00e4 (G)
+dialog.colourchooser.blue=Sininen (B)
+dialog.colourer.intro=Pisteen v\u00e4ritt\u00e4j\u00e4 voi tuottaa reittipisteille eri v\u00e4rej\u00e4
+dialog.colourer.type=V\u00e4ritt\u00e4j\u00e4n tyyppi
+dialog.colourer.type.none=Ei mit\u00e4\u00e4n
+dialog.colourer.type.byfile=Tiedosto
+dialog.colourer.type.bysegment=Lohko
+dialog.colourer.type.byaltitude=Korkeus
+dialog.colourer.type.byspeed=Nopeus
+dialog.colourer.type.byvertspeed=Pystysuuntainen nopeus
+dialog.colourer.type.bygradient=Kaltevuus
+dialog.colourer.type.bydate=P\u00e4iv\u00e4m\u00e4\u00e4r\u00e4
+dialog.colourer.start=Alkuv\u00e4ri
+dialog.colourer.end=P\u00e4\u00e4tev\u00e4ri
+dialog.colourer.maxcolours=V\u00e4rien enimm\u00e4ism\u00e4\u00e4r\u00e4
+dialog.setlanguage.firstintro=Voi valita jonkin julkaisun mukana toimitetuista kieliversioista,<p>tai valita jonkin k\u00e4\u00e4nn\u00f6kset sis\u00e4lt\u00e4v\u00e4n  tekstitiedoston.
+dialog.setlanguage.secondintro=<p>Kielen vaihtaaksesi sinun pit\u00e4\u00e4 ensiksi tallentaa asetukset ja sitten<p>k\u00e4ynnist\u00e4\u00e4 GpsPrune uudestaan.
+dialog.setlanguage.language=Kieli
+dialog.setlanguage.languagefile=Kielitiedosto
+dialog.setlanguage.endmessage=Tallenna nyt asetukset ja k\u00e4ynnist\u00e4 GpsPrune uudelleen\nsaadaksesi vaihtamasi kielen k\u00e4ytt\u00f6\u00f6n.
+dialog.setlanguage.endmessagewithautosave=K\u00e4ynnist\u00e4 GpsPrune ottaaksesi k\u00e4ytt\u00f6\u00f6n valitsemasi kielen.
+dialog.diskcache.save=Tallenna karttapalat v\u00e4limuistihakemistoon
+dialog.diskcache.dir=V\u00e4limuistin hakemisto
+dialog.diskcache.createdir=Luodaanko hakemisto
+dialog.diskcache.nocreate=V\u00e4limuistin hakemistoa ei luotu
+dialog.diskcache.cannotwrite=Karttapaloja ei voida tallentaa valittuun hakemistoon
+dialog.diskcache.table.path=Hakemistoolku
+dialog.diskcache.table.usedby=Used by
+dialog.diskcache.table.zoom=Zoom
+dialog.diskcache.table.tiles=Tiles
+dialog.diskcache.table.megabytes=Megabytes
+dialog.diskcache.tileset=Tileset
+dialog.diskcache.tileset.multiple=multiple
+dialog.diskcache.deleteold=Poista vanhan karttapalat
+dialog.diskcache.maximumage=Enimm\u00e4isik\u00e4 (p\u00e4ivi\u00e4)
+dialog.diskcache.deleteall=Poista kaikki kartapalat
+dialog.diskcache.deleted=Poistettu %d tiedostoa v\u00e4limuistista
+dialog.deletefieldvalues.intro=Valitse nykyvalinnasta poistettavat kent\u00e4t
+dialog.deletefieldvalues.nofields=Ei kentti\u00e4 poistettavaksi t\u00e4st\u00e4 valinnasta
+dialog.displaysettings.linewidth=Viivanpaksuus reiteille (1-4)
+dialog.displaysettings.antialias=K\u00e4yt\u00e4 antialiasointia
+dialog.displaysettings.waypointicons=Kohdepistekuvakkeet
+dialog.displaysettings.wpicon.default=Oletus
+dialog.displaysettings.wpicon.ringpt=Ympyr\u00e4merkkari
+dialog.displaysettings.wpicon.plectrum=Plektra
+dialog.displaysettings.wpicon.ring=Rengas
+dialog.displaysettings.wpicon.pin=Nuppineula
+dialog.displaysettings.size.small=Pieni
+dialog.displaysettings.size.medium=Keski
+dialog.displaysettings.size.large=Iso
+dialog.downloadosm.desc=Vahvista ladataksesi muokkaamattoman OSM-datan valitsemaltasi alueelta:
+dialog.searchwikipedianames.search=Etsi:
+dialog.weather.location=Sijainti
+dialog.weather.update=Ennuste p\u00e4ivitetty
+dialog.weather.sunrise=Auringonnousu
+dialog.weather.sunset=Auringonlasku
+dialog.weather.temperatureunits=L\u00e4mp\u00f6tilat
+dialog.weather.currentforecast=S\u00e4\u00e4tila nyt
+dialog.weather.dailyforecast=Ennuste p\u00e4iv\u00e4ksi
+dialog.weather.3hourlyforecast=Ennuste kolmeksi tunniksi
+dialog.weather.day.now=S\u00e4\u00e4tila nyt
+dialog.weather.day.today=T\u00e4\u00e4n\u00e4\u00e4n
+dialog.weather.day.tomorrow=Huomenna
+dialog.weather.day.monday=Maanantai
+dialog.weather.day.tuesday=Tiistai
+dialog.weather.day.wednesday=Keskiviikko
+dialog.weather.day.thursday=Torstai
+dialog.weather.day.friday=Perjantai
+dialog.weather.day.saturday=Lauantai
+dialog.weather.day.sunday=Sunnuntai
+dialog.weather.wind=Tuuli
+dialog.weather.temp=L\u00e4mp\u00f6tila
+dialog.weather.humidity=Kosteus
+dialog.weather.creditnotice=T\u00e4m\u00e4 s\u00e4\u00e4tieto on openweathermap.org:n tuottama. Lis\u00e4tietoja heid\u00e4n webbisivuiltaans.
+dialog.deletebydate.onlyonedate=Kaikki pisteet on tallennettu samana p\u00e4iv\u00e4n\u00e4.
+dialog.deletebydate.intro=Reitin kunkin p\u00e4iv\u00e4n osalta voit joko tuhota tai s\u00e4ilytt\u00e4\u00e4 reittipisteet
+dialog.deletebydate.nodate=Ei aikaleimaa
+dialog.deletebydate.column.keep=S\u00e4ilyt\u00e4
+dialog.deletebydate.column.delete=Poista
+dialog.setaltitudetolerance.text.metres=Raja-arvot (metreiss\u00e4), joita pienemm\u00e4t laskeutumisia tai nousuja ei huomioida
+dialog.setaltitudetolerance.text.feet=Raja-arvot (jaloissa), joita pienemm\u00e4t laskeutumisia tai nousuja ei huomioida
+dialog.settimezone.intro=T\u00e4ss\u00e4 voit valita aikavy\u00f6hykeen, jonka mukaan pisteiden aikaleimat n\u00e4ytet\u00e4\u00e4n
+dialog.settimezone.system=K\u00e4yt\u00e4 j\u00e4rjestelm\u00e4n aikavy\u00f6hykett\u00e4
+dialog.settimezone.custom=K\u00e4yt\u00e4 seuraavaa aikavy\u00f6kykett\u00e4:
+dialog.settimezone.list.toomany=Liian monta valittavaksi
+dialog.settimezone.selectedzone=Valittu aikavy\u00f6hyke
+dialog.settimezone.offsetfromutc=Aikaero UTC:n
+dialog.autoplay.duration=Kesto (sekuntia)
+dialog.autoplay.usetimestamps=K\u00e4yt\u00e4 pisteiden aikaleimoja
+dialog.autoplay.rewind=Alkuun
+dialog.autoplay.pause=Pys\u00e4yt\u00e4
+dialog.autoplay.play=Toista
+
+# 3d window
+dialog.3d.title=GpsPrune:n kolmiulotteinen n\u00e4kym\u00e4
+dialog.3d.altitudefactor=Korkeuden liioittelukerroin
+
+# Confirm messages
+confirm.loadfile=Paikkatieto ladattu tiedostosta
+confirm.save.ok1=Successfully saved
+confirm.save.ok2=points to file
+confirm.deletepoint.single=data point was removed
+confirm.deletepoint.multi=data points were removed
+confirm.point.edit=point edited
+confirm.mergetracksegments=Track segments merged
+confirm.reverserange=Range reversed
+confirm.addtimeoffset=Time offset added
+confirm.addaltitudeoffset=Altitude offset added
+confirm.rearrangewaypoints=Kohdepisteet j\u00e4rjestetty uudelleen
+confirm.rearrangephotos=Kuvat j\u00e4rjestetty uudelleen
+confirm.splitsegments=%d segment splits were made
+confirm.sewsegments=%d segment joins were made
+confirm.cutandmove=Selection moved
+confirm.interpolate=Points added
+confirm.convertnamestotimes=V\u00e4lipisteiden nimet muunnettu
+confirm.saveexif.ok=Saved %d photo files
+confirm.undo.single=operation undone
+confirm.undo.multi=operations undone
+confirm.jpegload.single=photo was added
+confirm.jpegload.multi=photos were added
+confirm.media.connect=media liitetty
+confirm.photo.disconnect=kuva liitt\u00e4m\u00e4t\u00e6n
+confirm.audio.disconnect=\u00e4\u00e4ni liitt\u00e4m\u00e4t\u00e6n
+confirm.media.removed=poistettu
+confirm.correlatephotos.single=kuva oli korreloitu
+confirm.correlatephotos.multi=kuvat olivat korreloidut
+confirm.rotatephoto=kuva kierretty
+confirm.createpoint=piste luotu
+confirm.running=Running ...
+confirm.lookupsrtm=L\u00f6ytyi %d korkeusarvoa
+confirm.downloadsrtm=Ladattu %d tiedostoa v\u00e4limuistiin
+confirm.downloadsrtm.1=Ladattu %d tiedosto v\u00e4limuistiin
+confirm.downloadsrtm.none=Tiedostoja ei ladattu, koska ne olivat jo v\u00e4limuistissa
+confirm.deletefieldvalues=Kentt\u00e4arvot poistettu
+confirm.audioload=\u00c4\u00e4nitiedostot lis\u00e4tty
+confirm.correlateaudios.single=\u00e4\u00e4ni oli korreloitu
+confirm.correlateaudios.multi=\u00e4\u00e4net olivat korreloidut
+
+# Tips, shown just once when appropriate
+tip.title=Vihje
+tip.useamapcache=Jos tallennutat karttapalat v\u00e4limuistina toimivaan hakemistoon levylle\n    Valikko: Asetukset -> Talleta kartat hakemistoon,\nn\u00e4yt\u00f6n toiminta nopeutuu ja verkkoliikenne v\u00e4hentyy.
+tip.learntimeparams=Tuloset voivat olla tarkempia, jos suoritutat toiminnon\n    Valikko: Reitti -> Opi aika-arvion parametrit\ntallentamillesi reiteille.
+tip.downloadsrtm=Voit nopeututtaa t\u00e4t\u00e4 suorituttamalla toiminnon\n    Valikko: Online -> Lataa SRTM-palat\nkarttojen v\u00e4limuistihakemistoon.
+tip.usesrtmfor3d=T\u00e4ss\u00e4 reitiss\u00e4 ei ole korkeustietoja.\nVoit k\u00e4ytt\u00e4\u00e4 SRTM-toimintoja saadaksesi likim\u00e4\u00e4r\u00e4iset\nkorkeusarvot 3D-n\u00e4kym\u00e4\u00e4 varten.
+tip.manuallycorrelateone=Jos korjaat v\u00e4hint\u00e4\u00e4n yhden kohteen aikatiedot, ohjelma voi laskea aikapoikkeamat puolestasi.
+
+# Buttons
+button.ok=OK
+button.back=Takaisin
+button.next=Seuraava
+button.finish=Loppuun
+button.cancel=Peru
+button.overwrite=Korvaa
+button.moveup=Siirry yl\u00f6s
+button.movedown=Siirry alas
+button.edit=Muokkaa
+button.exit=Lopeta
+button.close=Sulje
+button.continue=Jatka
+button.yes=Kyll\u00e4
+button.no=Ei
+button.yestoall=Kyll\u00e4 kaikkiin
+button.notoall=Ei kaikkiin
+button.always=Aina
+button.select=Valitse
+button.selectall=Valitse kaikki
+button.selectnone=Valitse 'ei mit\u00e4\u00e4n'
+button.preview=Esikatselu
+button.load=Lataa
+button.upload=Uppaa
+button.guessfields=Arvaa kent\u00e4t
+button.showwebpage=N\u00e4yt\u00e4 webbisivu
+button.check=Tarkista
+button.resettodefaults=Palauta oletusarvot
+button.browse=Selaa...
+button.addnew=Lis\u00e4\u00e4 uusi
+button.delete=Poista
+button.manage=Hallinnoi
+button.combine=Yhdist\u00e4
+
+# File types
+filetype.txt=TXT tiedostot
+filetype.jpeg=JPG tiedostot
+filetype.kmlkmz=KML, KMZ tiedostot
+filetype.kml=KML tiedostot
+filetype.kmz=KMZ tiedostot
+filetype.gpx=GPX tiedostot
+filetype.pov=POV tiedostot
+filetype.svg=SVG tiedostot
+filetype.png=PNG tiedostot
+filetype.audio=MP3, OGG, WAV tiedostot
+
+# Display components
+display.nodata=Reittidataa ei ladattuna
+display.noaltitudes=Reittidata ei sis\u00e4ll\u00e4 korkeustietoja
+display.notimestamps=Reittidata ei sis\u00e4ll\u00e4 aikaleimoja
+display.novalues=Reittidata ei sis\u00e4ll\u00e4 tietoja t\u00e4lle kent\u00e4lle
+details.trackdetails=Reitin tiedot
+details.notrack=Reitti\u00e4 ei ladattuna
+details.track.points=Reittipisteit\u00e4
+details.track.file=Tiedosto
+details.track.numfiles=Tiedostojen m\u00e4\u00e4r\u00e4
+details.pointdetails=Pisteen tiedot
+details.index.selected=Index
+details.index.of=...
+details.nopointselection=Yht\u00e4\u00e4n pistett\u00e4 ei valittuna
+details.photofile=Kuvatiedosto
+details.norangeselection=Pistealuetta ei valittuna
+details.rangedetails=Pistealueen tiedot
+details.range.selected=Valittu
+details.range.to=...
+details.altitude.to=...
+details.range.climb=Nousu
+details.range.descent=Lasku
+details.coordformat=Koordinaatisto
+details.distanceunits=V\u00e4limatkan yksikk\u00f6
+display.range.time.secs=s
+display.range.time.mins=m
+display.range.time.hours=t
+display.range.time.days=pv
+details.range.avespeed=Keskinopeus
+details.range.maxspeed=Enimm\u00e4isnopeus
+details.range.numsegments=Lohkojen m\u00e4\u00e4r\u00e4
+details.range.pace=Vauhti
+details.range.gradient=Kaltevuus
+details.lists.waypoints=Kohdepisteet
+details.lists.photos=Kuvat
+details.lists.audio=\u00d4\u00e4net
+details.photodetails=Kuvan tiedot
+details.nophoto=Kuvaa ei valittuna
+details.photo.loading=Lataan
+details.photo.bearing=Suunta
+details.media.connected=Liitetty
+details.media.fullpath=Tiedostopolku
+details.audiodetails=Audio details
+details.noaudio=\u00c4\u00e4nileikett\u00e4 ei valittuna
+details.audio.file=\u00c4\u00e4nitiedosto
+details.audio.playing=toistetaan...
+map.overzoom=Ei karttoja t\u00e4lle suurennussuhteelle
+
+# Field names
+fieldname.latitude=Leveysaste (Lat.)
+fieldname.longitude=Pituusaste (Long.)
+fieldname.altitude=Korkeus
+fieldname.timestamp=Aikaleima
+fieldname.time=Aika
+fieldname.date=P\u00e4iv\u00e4m\u00e4\u00e4r\u00e4
+fieldname.waypointname=Nimi
+fieldname.waypointtype=Tyyppi
+fieldname.newsegment=Reittilohko
+fieldname.custom=Mukautettu
+fieldname.prefix=Kentt\u00e4
+fieldname.distance=V\u00e4limatka
+fieldname.duration=Kesto
+fieldname.speed=Nopeus
+fieldname.verticalspeed=Pystysuora nopeus
+fieldname.description=Kuvaus
+fieldname.mediafilename=Median tiedostonimi
+
+# Measurement units
+units.original=Alkuper\u00e4inen
+units.default=Oletus
+units.metres=Metrit
+units.metres.short=m
+units.feet=Jalat
+units.feet.short=ft
+units.kilometres=Kilometrit
+units.kilometres.short=km
+units.kilometresperhour=kilometri\u00e4 tunnissa
+units.kilometresperhour.short=km/t
+units.miles=Mailit
+units.miles.short=mi
+units.milesperhour=mailia tunnissa
+units.milesperhour.short=mph
+units.nauticalmiles=Merimailit
+units.nauticalmiles.short=N.m.
+units.nauticalmilesperhour.short=kts
+units.metrespersec=metri\u00e4 sekunnissa
+units.metrespersec.short=m/s
+units.feetpersec=jalkaa sekunnissa
+units.feetpersec.short=ft/s
+units.hours=tunnit
+units.minutes=minuutit
+units.seconds=sekunnit
+units.degminsec=Asteet-minuutit-sekunnit
+units.degmin=Asteet-minuutit
+units.deg=Asteet
+units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
+
+# How to combine conditions, such as filters
+logic.and=ja
+logic.or=tai
+
+# External urls
+url.googlemaps=maps.google.co.uk
+wikipedia.lang=en
+openweathermap.lang=en
+webservice.peakfinder=Avaa Peakfinder.org
+webservice.geohack=Avaa Geohack-sivu
+
+# Cardinals for 3d plots
+cardinal.n=P
+cardinal.s=E
+cardinal.e=I
+cardinal.w=L
+
+# Undo operations
+undo.load=lataa data
+undo.loadphotos=lataa kuvat
+undo.loadaudios=lataa \u00e4\u00e4nileikkeet
+undo.editpoint=muokkaa reittipistett\u00e4
+undo.deletepoint=tuhoa reittipiste
+undo.removephoto=poista kuva
+undo.removeaudio=poista \u00e4\u00e4nileike
+undo.deleterange=tuhoa pistealue
+undo.croptrack=typist\u00e4 reitti
+undo.deletemarked=tuhoa merkityt reittipisteet
+undo.insert=liit\u00e4 pisteit\u00e4
+undo.reverse=k\u00e4\u00e4nteinen pistealue
+undo.mergetracksegments=yhdist\u00e4 reittilohkot
+undo.splitsegments=pilko reittilohkot
+undo.sewsegments=yhdist\u00e4 reittilohkot
+undo.addtimeoffset=lis\u00e4\u00e4 aikapoikkeama
+undo.addaltitudeoffset=lis\u00e4\u00e4 korkeuspoikkeama
+undo.rearrangewaypoints=j\u00e4rjest\u00e4 kohdepisteet
+undo.cutandmove=siirr\u00e4 section
+undo.connect=yhdist\u00e4
+undo.disconnect=irroita
+undo.correlatephotos=korreloi kuvat
+undo.rearrangephotos=j\u00e4rjest\u00e4 kuvat
+undo.rotatephoto=kierr\u00e4 kuvaa
+undo.createpoint=luo piste
+undo.convertnamestotimes=muunna nimet ajoiksi
+undo.lookupsrtm=lookup altitudes from SRTM
+undo.deletefieldvalues=poista kentt\u00e4arvot
+undo.correlateaudios=korreloi \u00e4\u00e4nileikkeet
+
+# Error messages
+error.save.dialogtitle=Datan tallennuksessa virhe
+error.save.nodata=Ei dataa tallennettavaksi
+error.save.failed=Data tallennus ep\u00e4onnistui tiedostoon
+error.saveexif.filenotfound=Kuvatiedoston etsint\u00e4 ep\u00e4onnistui
+error.saveexif.cannotoverwrite1=Kuvatiedosto
+error.saveexif.cannotoverwrite2=on kirjoitussuojattu ja sit\u00e4 ei voi korvata.\n\nLuodaanko kopio, johon kirjoitetaan EXIF-tiedot?
+error.saveexif.failed=Kuvien %d ei onnistuttu tallentamaan
+error.saveexif.forced=Kuvien %d -tietojen muutos pit\u00e4\u00e4 pakottaa
+error.load.dialogtitle=Virhe ladattaessa dataa
+error.load.noread=En voi lukea tiedostoa
+error.load.nopoints=Koordinaattitietoja ei l\u00f6ytynyt tiedostosta
+error.load.unknownxml=Tunnistamaton xml-muoto:
+error.load.noxmlinzip=ZIP-arkistosta ei l\u00f6ytynyt XML-tiedostoa
+error.load.othererror=Virhe luettaessa tiedostoa:
+error.jpegload.dialogtitle=Virhe ladattaessa kuvia
+error.jpegload.nofilesfound=Tiedostoja ei l\u00f6ytynyt
+error.jpegload.nojpegsfound='jpeg'-tiedostoja ei l\u00f6ytnyt
+error.jpegload.nogpsfound=GPS-tietoja ei l\u00f6ytnyt
+error.audioload.nofilesfound=\u00c4\u00e4nileikkeit\u00e4 ei l\u00f6ytnyt
+error.gpsload.unknown=Tuntematon virhe
+error.undofailed.title=Peruminen ei onnistunut
+error.undofailed.text=Toiminnon peruminen ep\u00e4onnistui
+error.function.noop.title=Toiminnolla ei ole vaikutusta
+error.rearrange.noop=Pisteiden uudelleen j\u00e4rjest\u00e4misell\u00e4 ei ollut vaikutusta
+error.function.notavailable.title=Toiminto ei k\u00e4yt\u00f6ss\u00e4
+error.function.nojava3d=T\u00e4m\u00e4 toiminto vaatii Java3D-kirjaston.\n\nAsenna se tai tarkista Javan hakemistopolkujen m\u00e4\u00e4ritykset.
+error.3d=Virhe 3D-n\u00e4kym\u00e4ss\u00e4
+error.readme.notfound=Lueminut-tiedostoa ei l\u00f6ytynyt
+error.osmimage.dialogtitle=Virhe ladattaessa karttapaloja
+error.osmimage.failed=Karttapalojen lataus ei onnistunut. Tarkista verkkoyhteytesi toimivuus.
+error.language.wrongfile=Valittu tiedosto ei n\u00e4yt\u00e4 olevan GpsPrune:n kielitiedosto tai se sis\u00e4lt\u00e4\u00e4 virheit\u00e4.
+error.convertnamestotimes.nonames=Kohdepisteiden nimi\u00e4 ei voitu muuntaa ajoiksi
+error.lookupsrtm.nonefound=N\u00e4iss\u00e4 pisteiss\u00e4 ei ole korkeustietoja
+error.lookupsrtm.nonerequired=Kaikilla pisteill\u00e4 on jo korkeustieto,\njoten ei ole mit\u00e4\u00e4n haettavaa
+error.gpsies.uploadnotok='gpsies'-palvelin palautti viestin:
+error.gpsies.uploadfailed=Tiedoston uppaus ep\u00e4onnistui virheilmoitukseen:
+error.showphoto.failed=Kuvan lataus ei onnistunut
+error.playaudiofailed=\u00c4\u00e4nileikkeen toisto ei onnistunut
+error.cache.notthere=Karttapalojen v\u00e4limuistihakemistoa ei l\u00f6ytynyt
+error.cache.empty=Karttapalojen v\u00e4limuistihakemisto on tyhj\u00e4
+error.cache.cannotdelete=Yht\u00e4\u00e4n karttapalaa ei voitu poistaa
+error.learnestimationparams.failed=En voi opetella parametrej\u00e4 t\u00e4st\u00e4 reitist\u00e4.\n\nKokeile ladata useampia reittej\u00e4.
+error.tracksplit.nosplit=Reitti\u00e4 ei voitu pilkkoa
+error.downloadsrtm.nocache=SRTP-tiedostoja ei voitu tallentaa.\n\nTarkista karttapalojen v\u00e4limuistihakemisto tai sen asetukset.
+error.sewsegments.nothingdone=Reittilohkoja ei voitu yhdist\u00e4\u00e4.\n\nReitiss\u00e4 on nyt %d lohkoa.
index 064c6e93a1b8fafe40ebb0bab581946bcab64670..ab796671c362410367a2256c82d5d90f6074f667 100644 (file)
@@ -39,7 +39,7 @@ menu.view.browser.yahoo=Cartes Yahoo
 menu.view.browser.bing=Cartes Bing
 menu.settings=Pr\u00e9f\u00e9rences
 menu.settings.onlinemode=Charger cartes depuis internet
-menu.settings.antialias=Anticr\u00e9nelage
+dialog.displaysettings.antialias=Anticr\u00e9nelage
 menu.settings.autosave=Sauver automatiquement en quittant
 menu.help=Aide
 # Popup menu for map
@@ -83,7 +83,6 @@ function.sendtogps=Envoyer donn\u00e9es au GPS
 function.exportkml=Exporter en KML
 function.exportgpx=Exporter en GPX
 function.exportpov=Exporter en POV
-function.exportsvg=Exporter en SVG
 function.exportimage=Exporter une image
 function.editwaypointname=\u00c9diter le nom du point
 function.compress=Compresser la trace
@@ -121,7 +120,6 @@ function.mapillary=Rechercher dans Mapillary
 function.downloadosm=T\u00e9l\u00e9charger les donn\u00e9es OSM de la zone
 function.duplicatepoint=Dupliquer le point
 function.setcolours=Choisir les couleurs
-function.setlinewidth=Choisir la largeur de ligne
 function.setlanguage=Choisir la langue
 function.connecttopoint=Relier au point
 function.disconnectfrompoint=D\u00e9tacher du point
@@ -261,10 +259,6 @@ dialog.baseimage.zoom=Zoom
 dialog.baseimage.incomplete=Image incompl\u00e8te
 dialog.baseimage.tiles=Dalles
 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.gradients=Utiliser des d\u00e9grad\u00e9s pour l'ombrage
 dialog.exportimage.drawtrack=Dessiner la trace sur la carte
 dialog.exportimage.drawtrackpoints=Dessiner les points de trace
 dialog.exportimage.textscalepercent=Facteur d'echelle du texte (%)
@@ -439,12 +433,12 @@ dialog.deletemarked.nonefound=Pas de donn\u00e9es \u00e0 effacer
 dialog.pastecoordinates.desc=Entrez ou collez les coordonn\u00e9es ici
 dialog.pastecoordinates.coords=Coordonn\u00e9es
 dialog.pastecoordinates.nothingfound=V\u00e9rifier les coordonn\u00e9es et essayez \u00e0 nouveau
-dialog.help.help=Consultez la page\n http://gpsprune.activityworkshop.net/\npour plus de d\u00e9tails et des manuels utilisateur.
+dialog.help.help=Consultez la page\n https://gpsprune.activityworkshop.net/\npour plus de d\u00e9tails et des manuels utilisateur.
 dialog.about.version=Version
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune est un programme pour charger, afficher et \u00e9diter des donn\u00e9es de r\u00e9cepteurs GPS.
 dialog.about.summarytext2=Distribu\u00e9 sous license Gnu GPL pour un usage et une am\u00e9lioration libres, ouverts et mondiaux.<br>La copie, la redistribution et la modification sont autoris\u00e9es et encourag\u00e9es<br>selon les conditions d\u00e9taill\u00e9es dans le fichier <code>license.txt</code> inclus.
-dialog.about.summarytext3=Consultez la page <code style="font-weight:bold">http://activityworkshop.net/</code> pour plus de d\u00e9tails et des manuels utilisateur.
+dialog.about.summarytext3=Consultez la page <code style="font-weight:bold">https://activityworkshop.net/</code> pour plus de d\u00e9tails et des manuels utilisateur.
 dialog.about.languages=Langues disponibles
 dialog.about.translatedby=Texte en fran\u00e7ais par Petrovsk, theYinYeti, R\u00e9mi et jmr.
 dialog.about.systeminfo=Info Syst\u00e8me
@@ -455,11 +449,6 @@ dialog.about.systeminfo.povray=Povray install\u00e9
 dialog.about.systeminfo.exiftool=Exiftool install\u00e9
 dialog.about.systeminfo.gpsbabel=Gpsbabel install\u00e9
 dialog.about.systeminfo.gnuplot=Gnuplot install\u00e9
-dialog.about.systeminfo.exiflib=Librairie Exif
-dialog.about.systeminfo.exiflib.internal=Interne
-dialog.about.systeminfo.exiflib.internal.failed=Interne (non-trouv\u00e9)
-dialog.about.systeminfo.exiflib.external=Externe
-dialog.about.systeminfo.exiflib.external.failed=Externe (non-trouv\u00e9)
 dialog.about.yes=Oui
 dialog.about.no=Non
 dialog.about.credits=Cr\u00e9dits
@@ -478,7 +467,7 @@ dialog.checkversion.newversion1=La version actuelle est maintenant la version
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=La nouvelle version est sortie le
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Pour t\u00e9l\u00e9charger la nouvelle version, aller \u00e0 http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Pour t\u00e9l\u00e9charger la nouvelle version, aller \u00e0 https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Vous pouvez utiliser ces raccourcis clavier \u00e0 la place de la souris
 dialog.keys.keylist=<table><tr><td>Touches-fl\u00e8ches</td><td>Faire d\u00e9filer la carte horizontalement et verticalement</td></tr><tr><td>Ctrl + gauche, Ctrl + droite</td><td>Choisir le point pr\u00e9c\u00e9dent ou suivant</td></tr><tr><td>Ctrl + haut, Ctrl + bas</td><td>Zoomer, s'\u00e9loigner</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Choisir le segment pr\u00e9c\u00e9dent ou suivant</td></tr><tr><td>Ctrl + Home, End</td><td>Choisir le point premier, dernier</td></tr><tr><td>Suppr</td><td>Effacer le point courant</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -556,7 +545,7 @@ dialog.diskcache.deleteall=Efface toutes les dalles
 dialog.diskcache.deleted=Effac\u00e9 %d dalles en cache
 dialog.deletefieldvalues.intro=Choisir le champ \u00e0 effacer pour l'\u00e9tendue actuelle
 dialog.deletefieldvalues.nofields=L'\u00e9tendue actuelle n'a pas de champs \u00e0 effacer
-dialog.setlinewidth.text=Entrer l'\u00e9paisseur des lignes des traces (1-4)
+dialog.displaysettings.linewidth=L'\u00e9paisseur des lignes des traces (1-4)
 dialog.downloadosm.desc=Confirmer le t\u00e9l\u00e9chargement des donn\u00e9es OSM brutes pour la zone indiqu\u00e9e :
 dialog.searchwikipedianames.search=Chercher :
 dialog.weather.location=Location
@@ -610,8 +599,6 @@ confirm.addtimeoffset=D\u00e9calage ajout\u00e9
 confirm.addaltitudeoffset=D\u00e9calage d'altitude ajout\u00e9
 confirm.rearrangewaypoints=Waypoints r\u00e9arrang\u00e9s
 confirm.rearrangephotos=Photos r\u00e9arrang\u00e9es
-confirm.splitsegments=%d s\u00e9parations ont \u00e9t\u00e9 effectu\u00e9es
-confirm.sewsegments=%d r\u00e9unifications ont \u00e9t\u00e9 effectu\u00e9es
 confirm.cutandmove=S\u00e9lection d\u00e9plac\u00e9e
 confirm.interpolate=Points ajout\u00e9s
 confirm.convertnamestotimes=Noms de waypoints convertis
@@ -802,7 +789,6 @@ wikipedia.lang=fr
 openweathermap.lang=fr
 webservice.peakfinder=Ouvrir Peakfinder.org
 webservice.geohack=Ouvrir la page Geohack
-webservice.panoramio=Ouvrir la carte Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -860,7 +846,6 @@ error.jpegload.dialogtitle=Erreur au chargement des photos
 error.jpegload.nofilesfound=Aucun fichier trouv\u00e9
 error.jpegload.nojpegsfound=Aucun fichier jpeg trouv\u00e9
 error.jpegload.nogpsfound=Aucune information GPS trouv\u00e9e
-error.jpegload.exifreadfailed=Information Exif illisible. Aucune information Exif ne peut \u00eatre lue\nsans une librairie interne ou externe.
 error.audioload.nofilesfound=Aucun fichier audio trouv\u00e9
 error.gpsload.unknown=Erreur inconnue
 error.undofailed.title=\u00c9chec de l'annulation
index b9495004bbd36099c34da7cc1a271f502ce35e7c..9b61e87ca68340721a124c4814f82f0227c33ac1 100644 (file)
@@ -38,7 +38,7 @@ menu.view.browser.yahoo=Yahoo! Maps
 menu.view.browser.bing=Bing Maps
 menu.settings=Be\u00e1ll\u00edt\u00e1sok
 menu.settings.onlinemode=T\u00e9rk\u00e9pek bet\u00f6lt\u00e9se internetr\u0151l
-menu.settings.antialias=\u00c9lsim\u00edt\u00e1s haszn\u00e1lata
+dialog.displaysettings.antialias=\u00c9lsim\u00edt\u00e1s haszn\u00e1lata
 menu.settings.autosave=Be\u00e1ll\u00edt\u00e1sok automatikus ment\u00e9se kil\u00e9p\u00e9skor
 menu.help=S\u00fag\u00f3
 
@@ -83,7 +83,6 @@ function.sendtogps=Adatok felt\u00f6lt\u00e9se GPS-re
 function.exportkml=Export\u00e1l\u00e1s KML-be
 function.exportgpx=Export\u00e1l\u00e1s GPX-be
 function.exportpov=Export\u00e1l\u00e1s POV-ba
-function.exportsvg=Export\u00e1l\u00e1s SVG-be
 function.exportimage=Export\u00e1l\u00e1s k\u00e9pbe
 function.editwaypointname=\u00datpont nev\u00e9nek szerkeszt\u00e9se
 function.compress=Nyomvonal t\u00f6m\u00f6r\u00edt\u00e9se
@@ -122,7 +121,6 @@ function.mapillary=F\u00e9nyk\u00e9pek keres\u00e9se Mapillary-n
 function.downloadosm=OSM adatok let\u00f6lt\u00e9se a ter\u00fcletr\u0151l
 function.duplicatepoint=Pont kett\u0151z\u00e9se
 function.setcolours=Sz\u00ednek be\u00e1ll\u00edt\u00e1sa
-function.setlinewidth=Vonalsz\u00e9less\u00e9g be\u00e1ll\u00edt\u00e1sa
 function.setlanguage=Nyelv be\u00e1ll\u00edt\u00e1sa
 function.connecttopoint=Kapcsol\u00e1s ponthoz
 function.disconnectfrompoint=Lev\u00e1laszt\u00e1s pontr\u00f3l
@@ -265,10 +263,6 @@ dialog.baseimage.zoom=Zoom szint
 dialog.baseimage.incomplete=Hi\u00e1nyos k\u00e9p
 dialog.baseimage.tiles=Csemp\u00e9k
 dialog.baseimage.size=K\u00e9pm\u00e9ret
-dialog.exportsvg.text=Param\u00e9terek kiv\u00e1laszt\u00e1sa az SVG exporthoz
-dialog.exportsvg.phi=Ir\u00e1nysz\u00f6g \u03d5
-dialog.exportsvg.theta=Emel\u00e9s sz\u00f6ge \u03b8
-dialog.exportsvg.gradients=\u00c1tmenetek haszn\u00e1lata az \u00e1rny\u00e9kol\u00e1shoz
 dialog.exportimage.noimagepossible=A t\u00e9rk\u00e9p k\u00e9peit az export\u00e1l\u00e1shoz el\u0151bb lemezre kell menteni.
 dialog.exportimage.drawtrack=Nyomvonal rajzol\u00e1sa a t\u00e9rk\u00e9pen
 dialog.exportimage.drawtrackpoints=A nyomvonal pontjainak kirajzol\u00e1sa
@@ -388,6 +382,7 @@ dialog.wikipedia.column.name=Sz\u00f3cikk neve
 dialog.wikipedia.column.distance=T\u00e1vols\u00e1g
 dialog.wikipedia.nonefound=Nem tal\u00e1lhat\u00f3 Wikip\u00e9dia sz\u00f3cikk
 dialog.wikipedia.gallery=Gal\u00e9ria
+dialog.geocaching.nonefound=Nem tal\u00e1lhat\u00f3 geol\u00e1da
 dialog.correlate.notimestamps=Nincsenek id\u0151b\u00e9lyegek az adatpontokon, \u00edgy nem feleltethet\u0151 meg semmi a f\u00e9nyk\u00e9pekkel.
 dialog.correlate.nouncorrelatedphotos=Nincsenek megfeleltetlen f\u00e9nyk\u00e9pek.\nBiztos benne, hogy folytatja?
 dialog.correlate.nouncorrelatedaudios=Nincsenek megfeleltetlen hangok.\nBiztos benne, hogy folytatja?
@@ -446,12 +441,12 @@ dialog.deletemarked.nonefound=Nem t\u00e1vol\u00edthat\u00f3 el adatpont
 dialog.pastecoordinates.desc=Adja meg vagy illessze be a koordin\u00e1t\u00e1kat ide
 dialog.pastecoordinates.coords=Koordin\u00e1t\u00e1k
 dialog.pastecoordinates.nothingfound=Ellen\u0151rizze a koordin\u00e1t\u00e1kat, \u00e9s pr\u00f3b\u00e1lja \u00fajra
-dialog.help.help=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a \n http://gpsprune.activityworkshop.net/\nwebhelyet.
+dialog.help.help=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a \n https://gpsprune.activityworkshop.net/\nwebhelyet.
 dialog.about.version=Verzi\u00f3
 dialog.about.build=Build
 dialog.about.summarytext1=A GpsPrune egy program GPS vev\u0151kr\u0151l sz\u00e1rmaz\u00f3 adatok bet\u00f6lt\u00e9s\u00e9re, megjelen\u00edt\u00e9s\u00e9re \u00e9s szerkeszt\u00e9s\u00e9re.
 dialog.about.summarytext2=Gnu GPL licenc alatt ker\u00fclt kiad\u00e1sra a szabad, ny\u00edlt, vil\u00e1gm\u00e9ret\u0171 haszn\u00e1lathoz \u00e9s fejleszt\u00e9shez.<br>M\u00e1sol\u00e1sa, terjeszt\u00e9se \u00e9s m\u00f3dos\u00edt\u00e1sa megengedett \u00e9s \u00f6szt\u00f6nz\u00f6tt<br>a mell\u00e9kelt <code>license.txt</code> f\u00e1jlban r\u00f6gz\u00edtett felt\u00e9telek szerint
-dialog.about.summarytext3=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a <code style="font-weight:bold">http://activityworkshop.net/</code> webhelyet.
+dialog.about.summarytext3=Tov\u00e1bbi inform\u00e1ci\u00f3k\u00e9rt \u00e9s kezel\u00e9si \u00fatmutat\u00f3\u00e9rt l\u00e1sd a <code style="font-weight:bold">https://activityworkshop.net/</code> webhelyet.
 dialog.about.languages=El\u00e9rhet\u0151 nyelvek
 dialog.about.translatedby=Magyar sz\u00f6veg: Ball\u00f3 Gy\u00f6rgy \u00e9s B\u00e1thory P\u00e9ter
 dialog.about.systeminfo=Rendszerinform\u00e1ci\u00f3
@@ -462,11 +457,6 @@ dialog.about.systeminfo.povray=Povray telep\u00edtve
 dialog.about.systeminfo.exiftool=Exiftool telep\u00edtve
 dialog.about.systeminfo.gpsbabel=Gpsbabel telep\u00edtve
 dialog.about.systeminfo.gnuplot=Gnuplot telep\u00edtve
-dialog.about.systeminfo.exiflib=Exif f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r
-dialog.about.systeminfo.exiflib.internal=Be\u00e9p\u00edtett
-dialog.about.systeminfo.exiflib.internal.failed=Be\u00e9p\u00edtett (nem tal\u00e1lhat\u00f3)
-dialog.about.systeminfo.exiflib.external=K\u00fcls\u0151
-dialog.about.systeminfo.exiflib.external.failed=K\u00fcls\u0151 (nem tal\u00e1lhat\u00f3)
 dialog.about.yes=Igen
 dialog.about.no=Nem
 dialog.about.credits=K\u00e9sz\u00edt\u0151k
@@ -485,7 +475,7 @@ dialog.checkversion.newversion1=El\u00e9rhet\u0151 a GpsPrune \u00faj verzi\u00f
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Az \u00faj verzi\u00f3 ekkor lett kiadva:
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Az \u00faj verzi\u00f3 let\u00f6lt\u00e9s\u00e9hez keresse fel a http://gpsprune.activityworkshop.net/download.html webhelyet.
+dialog.checkversion.download=Az \u00faj verzi\u00f3 let\u00f6lt\u00e9s\u00e9hez keresse fel a https://gpsprune.activityworkshop.net/download.html webhelyet.
 dialog.keys.intro=A k\u00f6vetkez\u0151 gyorsbillenty\u0171k haszn\u00e1lhat\u00f3k az eg\u00e9r haszn\u00e1lata helyett
 dialog.keys.keylist=<table><tr><td>Ny\u00edlbillenty\u0171k</td><td>T\u00e9rk\u00e9p mozgat\u00e1sa balra, jobbra, fel, le</td></tr><tr><td>Ctrl + bal, jobb ny\u00edl</td><td>El\u0151z\u0151 vagy k\u00f6vetkez\u0151 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + fel, le ny\u00edl</td><td>Nagy\u00edt\u00e1s vagy kicsiny\u00edt\u00e9s</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>El\u0151z\u0151, k\u00f6vetkez\u0151 szakasz kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Ctrl + Home, End</td><td>Els\u0151, utols\u00f3 pont kiv\u00e1laszt\u00e1sa</td></tr><tr><td>Del</td><td>Jelenlegi pont t\u00f6rl\u00e9se</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -564,7 +554,7 @@ dialog.diskcache.deleteall=Az \u00f6sszes csempe t\u00f6rl\u00e9se
 dialog.diskcache.deleted=%d f\u00e1jl t\u00f6r\u00f6lve a gyors\u00edt\u00f3t\u00e1rb\u00f3l
 dialog.deletefieldvalues.intro=V\u00e1lassza ki a t\u00f6rlend\u0151 mez\u0151t a jelenlegi tartom\u00e1nyban
 dialog.deletefieldvalues.nofields=Nincs t\u00f6r\u00f6lhet\u0151 mez\u0151 a tartom\u00e1nyban
-dialog.setlinewidth.text=Adja meg a rajzoland\u00f3 vonalak vastags\u00e1g\u00e1t a nyomvonalak sz\u00e1m\u00e1ra (1-4)
+dialog.displaysettings.linewidth=Adja meg a rajzoland\u00f3 vonalak vastags\u00e1g\u00e1t a nyomvonalak sz\u00e1m\u00e1ra (1-4)
 dialog.downloadosm.desc=Nyers OSM adatok let\u00f6lt\u00e9s\u00e9nek meger\u0151s\u00edt\u00e9se a megadott ter\u00fcletre:
 dialog.searchwikipedianames.search=Keres\u00e9s erre:
 dialog.weather.location=Helysz\u00edn
@@ -812,7 +802,6 @@ wikipedia.lang=hu
 openweathermap.lang=en
 webservice.peakfinder=Peakfinder.org megnyit\u00e1sa
 webservice.geohack=A geohack lap nyit\u00e1sa
-webservice.panoramio=Panoramio t\u00e9rk\u00e9p nyit\u00e1sa
 
 # Cardinals for 3d plots
 cardinal.n=\u00c9
@@ -870,7 +859,6 @@ error.jpegload.dialogtitle=Hiba a k\u00e9pek bet\u00f6lt\u00e9sekor
 error.jpegload.nofilesfound=Nem tal\u00e1lhat\u00f3 f\u00e1jl
 error.jpegload.nojpegsfound=Nem tal\u00e1lhat\u00f3 jpeg f\u00e1jl
 error.jpegload.nogpsfound=Nem tal\u00e1lhat\u00f3 GPS inform\u00e1ci\u00f3
-error.jpegload.exifreadfailed=Az Exif inform\u00e1ci\u00f3 olvas\u00e1sa nem siker\u00fclt. Nem olvashat\u00f3 Exif inform\u00e1ci\u00f3\nbe\u00e9p\u00edtett vagy k\u00fcls\u0151 f\u00fcggv\u00e9nyk\u00f6nyvt\u00e1r n\u00e9lk\u00fcl.
 error.audioload.nofilesfound=Nem tal\u00e1lhat\u00f3 hangf\u00e1jl
 error.gpsload.unknown=Ismeretlen hiba
 error.undofailed.title=A visszavon\u00e1s nem siker\u00fclt
index d8e68953e792b1bba354fa9c9da4f2c2ca9dd33d..892d88d69c88bc7b1410be5e5c2225365125e027 100644 (file)
@@ -39,7 +39,6 @@ menu.view.browser.yahoo=Mappe Yahoo
 menu.view.browser.bing=Mappe Bing
 menu.settings=Preferenze
 menu.settings.onlinemode=Carica mappa da internet
-menu.settings.antialias=Usa antialiasing
 menu.settings.autosave=Salva settaggi con chiusura del programma
 menu.help=Aiuto
 # Popup menu for map
@@ -83,7 +82,6 @@ function.sendtogps=Invia dati al GPS
 function.exportkml=Esporta in KML
 function.exportgpx=Esporta in GPX
 function.exportpov=Esporta in POV
-function.exportsvg=Esporta in SVG
 function.exportimage=Esporta come immagine
 function.editwaypointname=Modifica nome waypoint
 function.compress=Comprimi la traccia
@@ -110,18 +108,20 @@ function.setpaths=Configura percorsi programmi
 function.selectsegment=Seleziona segmento corrente
 function.splitsegments=Dividi traccia in segmenti
 function.sewsegments=Riorganizza segmenti insieme
+function.createmarkerwaypoints=Crea marcatori
 function.getgpsies=Ottieni tracce da Gpsies
 function.uploadgpsies=Carica traccia su Gpsies
 function.lookupsrtm=Ottieni quote da SRTM
 function.downloadsrtm=Scarica file da SRTM
-function.getwikipedia=Ottieni informazioni da Wikipedia
+function.getwikipedia=Ottieni i punti Wikipedia nelle vicinanze
 function.searchwikipedianames=Cerca il nome in Wikipedia
+function.searchosmpois=Ottieni i punti OSM nelle vicinanze
 function.searchopencachingde=Cerca OpenCaching.de
 function.mapillary=Cerca Mapillary.com
 function.downloadosm=Scarica dati OSM dell'area
 function.duplicatepoint=Duplica punto corrente in coda
 function.setcolours=Scegli colori
-function.setlinewidth=Scegli la lo spessore
+function.setdisplaysettings=Opzioni di visualizzazione
 function.setlanguage=Scegli la lingua
 function.connecttopoint=Collega al punto
 function.disconnectfrompoint=Scollega dal punto
@@ -146,6 +146,7 @@ function.diskcache=Salva mappe su disco
 function.managetilecache=Gestione del cache di tasselli
 function.getweatherforecast=Ottieni previsioni del tempo
 function.setaltitudetolerance=Configura tolleranza di altitudini
+function.selecttimezone=Seleziona fuso orario
 
 # Dialogs
 dialog.exit.confirm.title=Esci da GpsPrune
@@ -264,10 +265,6 @@ dialog.baseimage.zoom=Livello di zoom
 dialog.baseimage.incomplete=Immagine incompleta
 dialog.baseimage.tiles=Tiles
 dialog.baseimage.size=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.gradients=Usa il gradiente per le ombre
 dialog.exportimage.noimagepossible=Le mappe devono essere memorizzate su disco prima di poter essere esportate.
 dialog.exportimage.drawtrack=Disegna traccia sulla mappa
 dialog.exportimage.drawtrackpoints=Disegna punti
@@ -387,6 +384,9 @@ dialog.wikipedia.column.name=Titolo articolo
 dialog.wikipedia.column.distance=Distanza
 dialog.wikipedia.nonefound=Nessuna punti trovata
 dialog.wikipedia.gallery=Immagine
+dialog.osmpois.column.name=Nome
+dialog.osmpois.column.type=Tipo
+dialog.osmpois.nonefound=Nessuna punti trovata
 dialog.geocaching.nonefound=Nessuna punti trovata
 dialog.correlate.notimestamps=Non ci sono informazioni temporali tra i dati dei punti, non c'\u00e8 niente per collegarli con le foto.
 dialog.correlate.nouncorrelatedphotos=Non ci sono foto non correlate.\nSei sicuro di voler continuare?
@@ -446,12 +446,12 @@ dialog.deletemarked.nonefound=Nessun punto rimosso
 dialog.pastecoordinates.desc=Inserisci o incolla qui le coordinate
 dialog.pastecoordinates.coords=Coordinate
 dialog.pastecoordinates.nothingfound=Per favore, controlla le coordinate e riprova
-dialog.help.help=Per favore vedi\n http://gpsprune.activityworkshop.net/\nper maggiori informazioni e per la guida utente.
+dialog.help.help=Per favore vedi\n https://gpsprune.activityworkshop.net/\nper maggiori informazioni e per la guida utente.
 dialog.about.version=Versione
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune \u00e8 un programma per il caricamento, la visione e l'edit di dati provenienti da un GPS.
 dialog.about.summarytext2=\u00c8 rilasciato sotto la licenza Gnu GPL per l'uso gratuito e aperto ed il suo miglioramento, con validit\u00e0 mondiale.<br>La copia, la ridistribuzione sono permesse e incoraggiate<br>in accordo con i termini inclusi nel file <code>license.txt</code>.
-dialog.about.summarytext3=Per favore vedi <code style="font-weight:bold">http://activityworkshop.net/</code> per maggiori informazioni e per la guida utente.
+dialog.about.summarytext3=Per favore vedi <code style="font-weight:bold">https://activityworkshop.net/</code> per maggiori informazioni e per la guida utente.
 dialog.about.languages=Lingue disponibili
 dialog.about.translatedby=Testo italiano di Giovanni Sartor + altro
 dialog.about.systeminfo=Info di sistema
@@ -462,11 +462,6 @@ dialog.about.systeminfo.povray=Povray installato
 dialog.about.systeminfo.exiftool=Exiftool installato
 dialog.about.systeminfo.gpsbabel=Gpsbabel installato
 dialog.about.systeminfo.gnuplot=Gnuplot installato
-dialog.about.systeminfo.exiflib=Libreria exif
-dialog.about.systeminfo.exiflib.internal=Interna
-dialog.about.systeminfo.exiflib.internal.failed=Interna (non trovata)
-dialog.about.systeminfo.exiflib.external=Esterna
-dialog.about.systeminfo.exiflib.external.failed=Esterna (non trovata)
 dialog.about.yes=S\u00ec
 dialog.about.no=No
 dialog.about.credits=Crediti
@@ -485,7 +480,7 @@ dialog.checkversion.newversion1=Una nuova versione di GpsPrune \u00e8 disponibil
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Questa nuova versione \u00e8 stata rilasciata il
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Per scaricare la nuova versione vai a http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Per scaricare la nuova versione vai a https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=Puoi utilizzare i seguenti tast di scelta rapida al posto del mouse
 dialog.keys.keylist=<table><tr><td>Tasti freccia</td><td>Muovi mappa destra, sinistra, su, giu'</td></tr><tr><td>Ctrl + freccia destra, sinistra</td><td>Selezione punto successivo o precedente</td></tr><tr><td>Ctrl + freccia su, giu'</td><td>Zoom in o out</td></tr><tr><td>Ctrl + pagina su, giu'</td><td>Segmento successivo o precedente</tr><tr><td>Ctrl + Home, End</td><td>Punto primo o ultimo</td></tr><tr><td>Del</td><td>Cancella punto attuale</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -564,7 +559,17 @@ dialog.diskcache.deleteall=Cancellare tutti tasselli
 dialog.diskcache.deleted=Cancellati %d files dal cache
 dialog.deletefieldvalues.intro=Selezione il campo da cancellare dall'intervallo corrente
 dialog.deletefieldvalues.nofields=Non ci sono campi da cancellare nell'intervallo corrente
-dialog.setlinewidth.text=Specifica il tratteggio delle linee per disegnare la traccia (1-4)
+dialog.displaysettings.linewidth=Tratteggio delle linee per disegnare la traccia (1-4)
+dialog.displaysettings.antialias=Usa antialiasing
+dialog.displaysettings.waypointicons=Icone waypoint
+dialog.displaysettings.wpicon.default=Default
+dialog.displaysettings.wpicon.ringpt=Etichetta rotonda
+dialog.displaysettings.wpicon.plectrum=Plettro
+dialog.displaysettings.wpicon.ring=Anello
+dialog.displaysettings.wpicon.pin=Spillo da lavagna
+dialog.displaysettings.size.small=Piccole
+dialog.displaysettings.size.medium=Medie
+dialog.displaysettings.size.large=Grandi
 dialog.downloadosm.desc=Conferma lo scarico dei dati raw OSM per l'area specificata:
 dialog.searchwikipedianames.search=Cerca per:
 dialog.weather.location=Localit\u00e0
@@ -596,6 +601,12 @@ dialog.deletebydate.column.keep=Tieni
 dialog.deletebydate.column.delete=Cancella
 dialog.setaltitudetolerance.text.metres=Tolleranza di altitudini (metri)
 dialog.setaltitudetolerance.text.feet=Tolleranza di altitudini (feet)
+dialog.settimezone.intro=Qui puoi selezionare il fuso orario con cui visualizzare il timestamp dei punti
+dialog.settimezone.system=Utilizza il fuso orario di sistema
+dialog.settimezone.custom=Utilizza il seguente fuso orario:
+dialog.settimezone.list.toomany=Troppe zone
+dialog.settimezone.selectedzone=Fuso orario selezionato
+dialog.settimezone.offsetfromutc=Offset rispetto a UTC
 dialog.autoplay.duration=Durata (sec)
 dialog.autoplay.usetimestamps=Usa dati temporali
 dialog.autoplay.rewind=Riavvolga
@@ -812,7 +823,6 @@ wikipedia.lang=it
 openweathermap.lang=it
 webservice.peakfinder=Apri Peakfinder.org
 webservice.geohack=Apri la pagina Geohack
-webservice.panoramio=Apri la mappa Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -870,7 +880,6 @@ error.jpegload.dialogtitle=Errore nel caricamento delle foto
 error.jpegload.nofilesfound=File non trovato
 error.jpegload.nojpegsfound=File jpeg non trovato
 error.jpegload.nogpsfound=Informazioni GPS non trovate
-error.jpegload.exifreadfailed=Lettera dei dati Exif fallita. I dati Exif non possono\n essere letti senza una libreria interna o esterna.
 error.audioload.nofilesfound=Riprese audio non trovate
 error.gpsload.unknown=Errore sconosciuto
 error.undofailed.title=Impossibile annullare
index 087e8f7ab0973661d5565844930813481c5d86d7..4f9d7eecb383a9c75d2aed016abc61b531dab651 100644 (file)
@@ -79,7 +79,6 @@ function.sendtogps=GPS\u3078\u4fdd\u5b58
 function.exportkml=KML\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportgpx=GPX\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportpov=POV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
-function.exportsvg=SVG\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.exportimage=\u30a4\u30e1\u30fc\u30b8\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
 function.editwaypointname=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u306e\u540d\u524d\u3092\u7de8\u96c6
 function.compress=\u30c8\u30e9\u30c3\u30af\u3092\u5727\u7e2e
@@ -105,7 +104,6 @@ function.searchwikipedianames=\u540d\u524d\u3067Wikipedia\u3092\u691c\u7d22
 function.downloadosm=\u30a8\u30ea\u30a2\u306eOSM\u30c7\u30fc\u30bf\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9
 function.duplicatepoint=\u91cd\u8907\u70b9
 function.setcolours=\u8272\u3092\u8a2d\u5b9a
-function.setlinewidth=\u884c\u306e\u5e45\u3092\u8a2d\u5b9a
 function.setlanguage=\u8a00\u8a9e\u8a2d\u5b9a
 function.connecttopoint=\u70b9\u306b\u63a5\u7d9a
 function.disconnectfrompoint=\u70b9\u304b\u3089\u63a5\u7d9a\u89e3\u9664
@@ -332,12 +330,12 @@ dialog.deletemarked.nonefound=\u9664\u304f\u3053\u3068\u304c\u3067\u304d\u308b\u
 dialog.pastecoordinates.desc=\u5ea7\u6a19\u3092\u3053\u3053\u306b\u5165\u529b\u3059\u308b\u304b\u30da\u30fc\u30b9\u30c8\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.pastecoordinates.coords=\u5ea7\u6a19
 dialog.pastecoordinates.nothingfound=\u5ea7\u6a19\u306e\u78ba\u8a8d\u3092\u3057\u3066\u3001\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u304f\u3060\u3055\u3044\u3002
-dialog.help.help=\u8a73\u3057\u3044\u60c5\u5831\u3084\u3001\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001\nhttp://gpsprune.activityworkshop.net/ \u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
+dialog.help.help=\u8a73\u3057\u3044\u60c5\u5831\u3084\u3001\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001\nhttps://gpsprune.activityworkshop.net/ \u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.about.version=\u30d0\u30fc\u30b8\u30e7\u30f3
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune \u306f\u3001GPS\u53d7\u4fe1\u6a5f\u304b\u3089\u306e\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u307f\u3001\u8868\u793a\u3057\u3001\u7de8\u96c6\u3059\u308b\u305f\u3081\u306e\u30d7\u30ed\u30b0\u30e9\u30e0\u3067\u3059\u3002
 dialog.about.summarytext2=\u3053\u308c\u306f\u3001Gnu GPL\u306e\u4e0b\u306b\u516c\u958b\u3055\u308c\u3001\u81ea\u7531\u3067\u3001\u30aa\u30fc\u30d7\u30f3\u3067\u3001\u4e16\u754c\u4e2d\u3067\u3064\u304b\u3048\u3001\u62e1\u5f35\u53ef\u80fd\u3067\u3059\u3002<br><code>"license.txt"</code>\u306b\u304b\u304b\u308c\u3066\u3044\u308b\u6761\u4ef6\u306b\u3088\u308b\u3068\u3001<br>\u8907\u88fd\u30fb\u518d\u914d\u5e03\u30fb\u6539\u5909\u306f\u3001\u8a31\u3055\u308c\u3066\u304a\u308a\u3001\u304b\u3064\u5968\u52b1\u3055\u308c\u3066\u3044\u307e\u3059\u3002
-dialog.about.summarytext3=\u3082\u3063\u3068\u8a73\u3057\u3044\u60c5\u5831\u3084\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001<code style="font-weight:bold">http://activityworkshop.net/</code>\u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
+dialog.about.summarytext3=\u3082\u3063\u3068\u8a73\u3057\u3044\u60c5\u5831\u3084\u30e6\u30fc\u30b6\u30fc\u30ac\u30a4\u30c9\u306a\u3069\u306f\u3001<code style="font-weight:bold">https://activityworkshop.net/</code>\u3092\u898b\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.about.languages=\u5229\u7528\u53ef\u80fd\u306a\u8a00\u8a9e
 dialog.about.translatedby=\u65e5\u672c\u8a9e\u7ffb\u8a33\uff1aOpenStreetMap \u8ca2\u732e\u8005\u306enazotoko
 dialog.about.systeminfo=\u30b7\u30b9\u30c6\u30e0\u60c5\u5831
@@ -348,7 +346,6 @@ dialog.about.systeminfo.povray=Povray \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08
 dialog.about.systeminfo.exiftool=Exiftool \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08
 dialog.about.systeminfo.gpsbabel=Gpsbabel \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08
 dialog.about.systeminfo.gnuplot=Gnuplot \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08
-dialog.about.systeminfo.exiflib=Exif\u30e9\u30a4\u30d6\u30e9\u30ea
 dialog.about.yes=\u306f\u3044
 dialog.about.no=\u3044\u3044\u3048
 dialog.about.credits=\u30af\u30ec\u30b8\u30c3\u30c8
@@ -367,7 +364,7 @@ dialog.checkversion.newversion1=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u306e GpsPr
 dialog.checkversion.newversion2=\u3067\u3059\u3002
 dialog.checkversion.releasedate1=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u306f\u3001
 dialog.checkversion.releasedate2=\u306b\u30ea\u30ea\u30fc\u30b9\u3057\u307e\u3057\u305f\u3002
-dialog.checkversion.download=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u306b\u306f\u3001 http://gpsprune.activityworkshop.net/download.html \u3078\u884c\u3063\u3066\u304f\u3060\u3055\u3044\u3002
+dialog.checkversion.download=\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b\u306b\u306f\u3001 https://gpsprune.activityworkshop.net/download.html \u3078\u884c\u3063\u3066\u304f\u3060\u3055\u3044\u3002
 dialog.keys.intro=\u30de\u30a6\u30b9\u306e\u4ee3\u308f\u308a\u306b\u6b21\u306e\u30b7\u30e7\u30fc\u30c8\u30ab\u30c3\u30c8\u30ad\u30fc\u3092\u4f7f\u3046\u4e8b\u304c\u3067\u304d\u307e\u3059\u3002
 dialog.keys.keylist=<table><tr><td>\u77e2\u5370\u30ad\u30fc</td><td>\u5730\u56f3\u3092\u4e0a\u4e0b\u5de6\u53f3\u306b\u79fb\u52d5</td></tr><tr><td>Ctrl + \u5de6\u30fb\u53f3\u77e2\u5370</td><td>\u524d\u30fb\u6b21\u306e\u70b9\u3092\u9078\u629e</td></tr><tr><td>Ctrl + \u4e0a\u30fb\u4e0b\u77e2\u5370</td><td>\u62e1\u5927\u30fb\u7e2e\u5c0f</td></tr><tr><td>Del</td><td>\u73fe\u5728\u306e\u70b9\u3092\u524a\u9664</td></tr></table>
 dialog.keys.normalmodifier=Ctrl\u30ad\u30fc
@@ -415,7 +412,7 @@ dialog.diskcache.save=\u30c7\u30a3\u30b9\u30af\u306b\u30a4\u30e1\u30fc\u30b8\u30
 dialog.diskcache.dir=\u30ad\u30e3\u30c3\u30b7\u30e5\u30c7\u30a3\u30ec\u30af\u30c8\u30ea
 dialog.diskcache.createdir=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210
 dialog.diskcache.nocreate=\u30ad\u30e3\u30c3\u30b7\u30e5\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u4f5c\u6210\u3055\u308c\u306a\u304b\u3063\u305f\u3002
-dialog.setlinewidth.text=\u30c8\u30e9\u30c3\u30af\u63cf\u753b\u306e\u7dda\u5e45\u30921-4\u306e\u7bc4\u56f2\u3067\u5165\u529b
+dialog.displaysettings.linewidth=\u30c8\u30e9\u30c3\u30af\u63cf\u753b\u306e\u7dda\u5e45\u30921-4\u306e\u7bc4\u56f2\u3067\u5165\u529b
 dialog.searchwikipedianames.search=\u53f3\u8a18\u3092\u691c\u7d22:
 
 # 3d window
index e1f1945493de05678b8ff07b7e4aba4a6a6c4661..21c0c60fce620e0f5e90750606abd6258c758e11 100644 (file)
@@ -77,7 +77,6 @@ function.sendtogps=GPS\ub85c \uc790\ub8cc \ubcf4\ub0b4\uae30
 function.exportkml=KML \ub0b4\ubcf4\ub0b4\uae30
 function.exportgpx=GPX \ub0b4\ubcf4\ub0b4\uae30
 function.exportpov=POV \ub0b4\ubcf4\ub0b4\uae30
-function.exportsvg=SVG \ub0b4\ubcf4\ub0b4\uae30
 function.editwaypointname=\uacbd\uc720\uc9c0 \uc774\ub984 \uc218\uc815
 function.compress=\uacbd\ub85c \uc555\ucd95\ud558\uae30
 function.addtimeoffset=\uc624\ud504\uc14b \uc2dc\uac04 \ucd94\uac00
@@ -100,7 +99,6 @@ function.searchwikipedianames=\uc774\ub984\uc73c\ub85c \uc704\ud0a4\ud53c\ub514\
 function.downloadosm=OSM \ub370\uc774\ud0c0 \ub2e4\uc6b4\ub85c\ub4dc\ud558\uae30
 function.duplicatepoint=\uc9c0\uc810 \ubcf5\uc0ac\ud558\uae30
 function.setcolours=\uc0c9\uc0c1 \uc9c0\uc815
-function.setlinewidth=\uc120 \ub113\uc774 \uc9c0\uc815
 function.setlanguage=\uc5b8\uc5b4 \uc9c0\uc815
 function.connecttopoint=\uc9c0\uc810\uc73c\ub85c \uc5f0\uacb0
 function.disconnectfrompoint=\uc9c0\uc810\uc5d0\uc11c \uc5f0\uacb0 \ub04a\uae30
@@ -194,10 +192,6 @@ dialog.exportpov.modelstyle=\ubaa8\ub378 \uc2a4\ud0c0\uc77c
 dialog.exportpov.ballsandsticks=\ub9c9\ub300\uae30\uc640 \uacf5
 dialog.exportpov.tubesandwalls=\ubcbd\uacfc \ud29c\ube0c
 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.gradients=\uc250\uc774\ub529\uc5d0 \uadf8\ub798\ub514\uc5b8\ud2b8 \uc0ac\uc6a9\ud558\uae30
 dialog.pointtype.desc=\uc9c0\uc810 \ud615\uc2dd \uc800\uc7a5\ud558\uae30
 dialog.pointtype.track=\ud2b8\ub799 \uc9c0\uc810
 dialog.pointtype.waypoint=\uacbd\uc720 \uc9c0\uc810
@@ -335,12 +329,12 @@ dialog.compress.summarylabel=\uc0ad\uc81c\ud560 \uc9c0\uc810
 dialog.pastecoordinates.desc=\uc790\ud45c\ub97c \ub123\uc73c\uc138\uc694
 dialog.pastecoordinates.coords=\uc88c\ud45c
 dialog.pastecoordinates.nothingfound=\uc88c\ud45c\ub97c \ud655\uc778\ud558\uc2dc\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \ubcf4\uc138\uc694.
-dialog.help.help=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n http://gpsprune.activityworkshop.net/
+dialog.help.help=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.\n https://gpsprune.activityworkshop.net/
 dialog.about.version=\ubc84\uc804
 dialog.about.build=\ube4c\ub4dc
 dialog.about.summarytext1=GpsPrune\uc740 GPS\uc218\uc2e0\uae30\uc5d0\uc11c \uc704\uce58 \uc815\ubcf4\ub97c \ubc1b\uace0, \ud654\uba74\uc5d0 \ubcf4\uc5ec\uc8fc\uace0, \uc218\uc815\ud558\uac8c \ud574\uc8fc\ub294 \ud504\ub85c\uadf8\ub7a8\uc785\ub2c8\ub2e4.
 dialog.about.summarytext2=\uc774 \ud504\ub85c\uadf8\ub7a8\uc740 \uc790\uc720\ub86d\uac8c, \uac1c\ubc29\uc801\uc73c\ub85c, \uadf8\ub9ac\uace0 \uc804\uc138\uacc4\uc801\uc73c\ub85c \uc0ac\uc6a9\ud558\uace0 \uac1c\uc120\ud558\uae30 \uc704\ud574 Gnu GPL \uc5d0 \ub530\ub77c\uc11c \ubc30\ud3ec\ub429\ub2c8\ub2e4. <br> \uc774 \ud504\ub85c\uadf8\ub7a8\uc5d0 \ud3ec\ud568\ub41c <code>license.txt</code> \ud30c\uc77c\uc758 \uc870\uac74\uc5d0 \ub530\ub77c \ubcf5\uc81c, \uc7ac\ubc30\ud3ec, \uc218\uc815\uc774 \ud5c8\uac00\ub418\uace0, \uc7a5\ub824\ub429\ub2c8\ub2e4.
-dialog.about.summarytext3=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.<code style="font-weight:bold">http://activityworkshop.net/</code></span>
+dialog.about.summarytext3=\ub354 \uc790\uc138\ud55c \uc815\ubcf4\ub098 \uc0ac\uc6a9\uc790\uc124\uba85\uc740 \uc774\uacf3\uc744 \ucc38\uace0\ud574\uc8fc\uc138\uc694.<code style="font-weight:bold">https://activityworkshop.net/</code></span>
 dialog.about.languages=\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc5b8\uc5b4\ub4e4
 dialog.about.translatedby=\ud55c\uad6d\uc5b4 Hooau
 dialog.about.systeminfo=\uc2dc\uc2a4\ud15c \uc815\ubcf4
@@ -351,11 +345,6 @@ dialog.about.systeminfo.povray=Povray\uac00 \uc124\uce58\ub418\uc5c8\uc74c
 dialog.about.systeminfo.exiftool=Exiftool\uc774 \uc124\uce58\ub418\uc5c8\uc74c
 dialog.about.systeminfo.gpsbabel=Gpsbabel\uc774 \uc124\uce58\ub418\uc5c8\uc74c
 dialog.about.systeminfo.gnuplot=Gnuplot\uc774 \uc124\uce58\ub418\uc5c8\uc74c
-dialog.about.systeminfo.exiflib=Exif \ub77c\uc774\ube0c\ub7ec\ub9ac
-dialog.about.systeminfo.exiflib.internal=\ub0b4\uc7a5
-dialog.about.systeminfo.exiflib.internal.failed=\ub0b4\uc7a5(\ucc3e\uc9c0\ubabb\ud568)
-dialog.about.systeminfo.exiflib.external=\uc678\uc7a5
-dialog.about.systeminfo.exiflib.external.failed=\uc678\uc7a5(\ucc3e\uc9c0\ubabb\ud568)
 dialog.about.yes=\uc608
 dialog.about.no=\uc544\ub2c8\uc624
 dialog.about.credits=\uc5d0\uac8c \uac10\uc0ac\ub97c
@@ -374,7 +363,7 @@ dialog.checkversion.newversion1=GpsPrune\uc758 \uc0c8 \ubc84\uc804\uc744 \uc9c0\
 dialog.checkversion.newversion2=\ubc84\uc804\uc785\ub2c8\ub2e4.
 dialog.checkversion.releasedate1=\uc0c8 \ubc84\uc804\uc740
 dialog.checkversion.releasedate2=\uc5d0 \ubc30\ud3ec\ub418\uc5c8\uc2b5\ub2c8\ub2e4.
-dialog.checkversion.download=\uc0c8 \ubc84\uc804\uc744 \ub2e4\uc6b4\ubc1b\uace0 \uc2f6\uc73c\uc138\uc694? \uadf8\ub7fc \uc544\ub798 URL\ub85c \uc640\uc8fc\uc138\uc694. /n http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=\uc0c8 \ubc84\uc804\uc744 \ub2e4\uc6b4\ubc1b\uace0 \uc2f6\uc73c\uc138\uc694? \uadf8\ub7fc \uc544\ub798 URL\ub85c \uc640\uc8fc\uc138\uc694. /n https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=\ub9c8\uc6b0\uc2a4\ub97c \uc0ac\uc6a9\ud558\uc9c0 \ub9c8\uc2dc\uace0 \uc544\ub798 \ub2e8\ucd95\ud0a4\ub97c \uc0ac\uc6a9\ud574\ubcf4\uc138\uc694.
 dialog.keys.keylist=<table><tr><td>\ud654\uc0b4\ud45c \ud0a4</td><td>\uc88c,\uc6b0,\uc544\ub798,\uc704\ub85c \uc9c0\ub3c4 \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc67c\ucabd, \uc624\ub978\ucabd \ud654\uc0b4\ud45c</td><td>\uc9c0\uc810 \uc120\ud0dd \uc774\ub3d9</td></tr><tr><td>Ctrl + \uc704, \uc544\ub798 \ud654\uc0b4\ud45c</td><td>\uc9c0\ub3c4 \ud655\ub300 \ucd95\uc18c</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\uc774\uc804 \uc774\ud6c4 \ubd80\ubd84 \uc120\ud0dd</td></tr><tr><td>Ctrl + Home, End</td><td>\ucc98\uc74c \uc9c0\uc810, \ub9c8\uc9c0\ub9c9 \uc9c0\uc810 \uc120\ud0dd</td></tr><tr><td>Del</td><td>\ud604\uc7ac \uc9c0\uc810 \uc0ad\uc81c</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -425,7 +414,7 @@ dialog.diskcache.dir=\uce90\uc2dc \ub514\ub809\ud1a0\ub9ac
 dialog.diskcache.createdir=\ub514\ub809\ud1a0\ub9ac \ub9cc\ub4e4\uae30
 dialog.diskcache.nocreate=\uce90\uc2dc \ub514\ub809\ud1a0\ub9ac\uac00 \ub9cc\ub4e4\uc5b4\uc9c0\uc9c0 \uc54a\uc558\uc5b4\uc694.
 dialog.deletefieldvalues.intro=\ud604\uc7ac \ubc94\uc704\uc5d0\uc11c \uc0ad\uc81c\ud560 \ud544\ub4dc\ub97c \uc120\ud0dd
-dialog.setlinewidth.text=\ud2b8\ub799\uc744 \uadf8\ub9b4 \uc120\uc758 \ub450\uaed8\ub97c \uc801\uc73c\uc138\uc694(1-4)
+dialog.displaysettings.linewidth=\ud2b8\ub799\uc744 \uadf8\ub9b4 \uc120\uc758 \ub450\uaed8\ub97c \uc801\uc73c\uc138\uc694(1-4)
 dialog.downloadosm.desc=\ud2b9\uc815\uc9c0\uc5ed\uc758 raw OSM \ub370\uc774\ud130\ub97c \ub2e4\uc6b4\ub85c\ub4dc \ud558\uc2dc\uaca0\uc5b4\uc694?
 dialog.searchwikipedianames.search=\ucc3e\uae30
 
@@ -653,7 +642,6 @@ error.jpegload.dialogtitle=\uc0ac\uc9c4 \ubd88\ub7ec\uc624\ub294 \uc911 \uc5d0\u
 error.jpegload.nofilesfound=\ucc3e\uc740 \ud30c\uc77c \uc5c6\uc74c.
 error.jpegload.nojpegsfound=\ucc3e\uc740 jpeg\ud30c\uc77c \uc5c6\uc74c.
 error.jpegload.nogpsfound=GPS \uc815\ubcf4\ub97c \ucc3e\uc9c0 \ubabb\ud568.
-error.jpegload.exifreadfailed=Exif\uc815\ubcf4 \uc77d\uae30 \uc2e4\ud328./n\ub0b4\uc7a5\uc774\ub098 \uc678\uc7a5 \ub77c\uc774\ube0c\ub7ec\uc774\uac00 \uc5c6\uc73c\uba74/nExif\uc815\ubcf4\ub97c \uc77d\uc744 \uc218 \uc5c6\uc5b4\uc694.
 error.audioload.nofilesfound=\ucc3e\uc740 \uc18c\ub9ac\ud30c\uc77c \uc5c6\uc74c.
 error.gpsload.unknown=\uc54c\ub824\uc9c0\uc9c0 \uc54a\uc740 \uc624\ub958.
 error.undofailed.title=\ub418\ub3cc\ub9ac\uae30 \uc2e4\ud328.
index 47b96016ea400bc263c8a6971d253543db2df4d7..fb5006d793b875b2c3adc066bb8b9ad331b6188e 100644 (file)
@@ -1,5 +1,5 @@
 # Text entries for the GpsPrune application
-# Dutch entries
+# Dutch entries thanks to Jeroen
 
 # Menu entries
 menu.file=Bestand
@@ -38,9 +38,9 @@ menu.view.browser.yahoo=Yahoo maps
 menu.view.browser.bing=Bing maps
 menu.settings=Instellingen
 menu.settings.onlinemode=Kaarten van internet ophalen
-menu.settings.antialias=Gebruik anti-aliasering
 menu.settings.autosave=Automatisch opslaan bij afsluiten
 menu.help=Help
+
 # Popup menu for map
 menu.map.zoomin=Zoom +
 menu.map.zoomout=Zoom -
@@ -82,7 +82,6 @@ function.sendtogps=Gegevens verzenden naar GPS
 function.exportkml=Export KML
 function.exportgpx=Export GPX
 function.exportpov=Export POV
-function.exportsvg=Export SVG
 function.exportimage=Bestand exporteren
 function.editwaypointname=Hernoem waypoint
 function.compress=Route comprimeren
@@ -110,18 +109,20 @@ function.setpaths=Instellen programmapaden
 function.selectsegment=Selecteer huidige segment
 function.splitsegments=Splits route in segmenten
 function.sewsegments=Voeg segmenten samen
+function.createmarkerwaypoints=aak waypoints voor markering
 function.getgpsies=Routes van Gpsies ophalen
 function.uploadgpsies=Upload routes naar Gpsies
 function.lookupsrtm=Hoogtes van SRTM ophalen
 function.downloadsrtm=Downloaden SRTM tegels
 function.getwikipedia=Wikipedia artikelen uit de buurt ophalen
 function.searchwikipedianames=Wikipedia zoeken op naam
+function.searchosmpois=Haal nabije OSM punten op
 function.searchopencachingde=Doorzoek OpenCaching.de
 function.mapillary=Zoek naar foto's op Mapillary
 function.downloadosm=Downloaden OSM data voor gebied
 function.duplicatepoint=Dupliceer punt
 function.setcolours=Instellen kleuren
-function.setlinewidth=Instellen lijndikte
+function.setdisplaysettings=Instellen afbeeldingopties
 function.setlanguage=Instellen taal
 function.connecttopoint=Aan punt vastzetten
 function.disconnectfrompoint=Van punt losmaken
@@ -146,6 +147,7 @@ function.diskcache=Kaart opslaan op schijf
 function.managetilecache=Beheer tegelcache
 function.getweatherforecast=Ophalen weersvoorspelling
 function.setaltitudetolerance=Instellen hoogtetolerantie
+function.selecttimezone=Instellen tijdzone
 
 # Dialogs
 dialog.exit.confirm.title=GpsPrune afsluiten
@@ -264,10 +266,6 @@ dialog.baseimage.zoom=Zoom niveau
 dialog.baseimage.incomplete=Afbeelding onvolledig
 dialog.baseimage.tiles=Tegels
 dialog.baseimage.size=Afbeeldinggrootte
-dialog.exportsvg.text=Selecteer de camera hoeken voor SVG export
-dialog.exportsvg.phi=Azimut hoek \u03d5
-dialog.exportsvg.theta=Stijgingshoek \u03b8
-dialog.exportsvg.gradients=Gebruik gradaties voor schaduw
 dialog.exportimage.noimagepossible=Kaartafbeeldingen dienen gecached te worden naar disk om ze te kunnen exporteren.
 dialog.exportimage.drawtrack=Teken route op kaart
 dialog.exportimage.drawtrackpoints=Teken routepunten
@@ -387,6 +385,9 @@ dialog.wikipedia.column.name=Artikelnaam
 dialog.wikipedia.column.distance=Afstand
 dialog.wikipedia.nonefound=Geen punten gevonden
 dialog.wikipedia.gallery=Galerie
+dialog.osmpois.column.name=Naam
+dialog.osmpois.column.type=Type
+dialog.osmpois.nonefound=Geen punten gevonden
 dialog.geocaching.nonefound=Geen punten gevonden
 dialog.correlate.notimestamps=Er zit geen tijdinformatie in de punten, dus kunnen ze niet aan foto's gekoppeld worden.
 dialog.correlate.nouncorrelatedphotos=Er zijn geen ongekoppelde foto's.\nWeet u zeker dat u wilt doorgaan?
@@ -446,12 +447,12 @@ dialog.deletemarked.nonefound=Er konden geen punten verwijderd worden
 dialog.pastecoordinates.desc=Geef co\u00f6rdinaten in
 dialog.pastecoordinates.coords=Co\u00f6rdinaten
 dialog.pastecoordinates.nothingfound=Controleer de co\u00f6rdinaten en probeer het nogmaals
-dialog.help.help=Ga naar\n http://gpsprune.activityworkshop.net/\nvoor meer informatie en handleidingen.
+dialog.help.help=Ga naar\n https://gpsprune.activityworkshop.net/\nvoor meer informatie en handleidingen.
 dialog.about.version=Versie
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune is een programma voor het laden, tonen en wijzigen van data uit GPS ontvangers.
 dialog.about.summarytext2=Uitgebracht onder de Gnu GPL voor gratis, open, wereldwijd gebruik en verbetering<br>Kopieren, verspreiding en aanpassing is toegestaan en aangemoedigd<br>volgens de voorwaarden in het bestand <code>license.txt</code>.
-dialog.about.summarytext3=Ga naar <code style="font-weight:bold">http://activityworkshop.net/</code> voor meer informatie en handleidingen.
+dialog.about.summarytext3=Ga naar <code style="font-weight:bold">https://activityworkshop.net/</code> voor meer informatie en handleidingen.
 dialog.about.languages=Beschikbare talen
 dialog.about.translatedby=Nederlandse vertaling door JFO
 dialog.about.systeminfo=Systeeminformatie
@@ -462,11 +463,6 @@ dialog.about.systeminfo.povray=Povray ge\u00efnstalleerd
 dialog.about.systeminfo.exiftool=Exiftool ge\u00efnstalleer
 dialog.about.systeminfo.gpsbabel=Gpsbabel ge\u00efnstalleer
 dialog.about.systeminfo.gnuplot=Gnuplot ge\u00efnstalleer
-dialog.about.systeminfo.exiflib=Exif bibliotheek
-dialog.about.systeminfo.exiflib.internal=Intern
-dialog.about.systeminfo.exiflib.internal.failed=Intern (niet gevonden)
-dialog.about.systeminfo.exiflib.external=Extern
-dialog.about.systeminfo.exiflib.external.failed=Extern (niet gevonden)
 dialog.about.yes=Ja
 dialog.about.no=Nee
 dialog.about.credits=Credits
@@ -485,7 +481,7 @@ dialog.checkversion.newversion1=Een nieuwe versie van GpsPrune is beschikbaar. D
 dialog.checkversion.newversion2=.
 dialog.checkversion.releasedate1=Deze nieuwe versie was vrijgegeven op
 dialog.checkversion.releasedate2=.
-dialog.checkversion.download=Om de nieuwst versie te downloaden, ga naar http://gpsprune.activityworkshop.net/download.html.
+dialog.checkversion.download=Om de nieuwst versie te downloaden, ga naar https://gpsprune.activityworkshop.net/download.html.
 dialog.keys.intro=De volgende sneltoetsen vervangen muisacties
 dialog.keys.keylist=<table><tr><td>Pijltjestoetsen</td><td>verschuif de kaart links, rechts, omhoog, omlaag</td></tr><tr><td>Ctrl + pijltje naar links, rechts</td><td>Selecteer volgende, vorige punt</td></tr><tr><td>Ctrl + pijltje omhoog, omlaag</td><td>Zoom in of uit</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>Selecteer vorig, volgend segment</td></tr><tr><td>Ctrl + Home, End</td><td>Select eerste, laatste punt</td></tr><tr><td>Del</td><td>Verwijder huidige punt</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
@@ -564,7 +560,17 @@ dialog.diskcache.deleteall=Verwijder alle tegels
 dialog.diskcache.deleted=%d bestanden uit de cache verwijderd
 dialog.deletefieldvalues.intro=Selecteer het te verwijderen veld voor de huidige reeks
 dialog.deletefieldvalues.nofields=Er zijn geen velden in deze reeks om te verwijderen
-dialog.setlinewidth.text=Geef lijndikte voor routes (1-4)
+dialog.displaysettings.linewidth=Geef lijndikte voor routes (1-4)
+dialog.displaysettings.antialias=Gebruik anti-aliasering
+dialog.displaysettings.waypointicons=Waypoint iconen
+dialog.displaysettings.wpicon.default=Standaard
+dialog.displaysettings.wpicon.ringpt=Rond markeerpunt
+dialog.displaysettings.wpicon.plectrum=Plectrum
+dialog.displaysettings.wpicon.ring=Ring
+dialog.displaysettings.wpicon.pin=Punaise
+dialog.displaysettings.size.small=Klein
+dialog.displaysettings.size.medium=Middel
+dialog.displaysettings.size.large=Groot
 dialog.downloadosm.desc=Bevestig het downloaden van ruwe OSM data voor dit gebied:
 dialog.searchwikipedianames.search=Zoeken naar:
 dialog.weather.location=Locatie
@@ -596,11 +602,18 @@ dialog.deletebydate.column.keep=Behouden
 dialog.deletebydate.column.delete=Verwijderen
 dialog.setaltitudetolerance.text.metres=Grens (in meters) waaronder kleine klimmen en afdalingen worden genegeerd
 dialog.setaltitudetolerance.text.feet=Grens (in feet) waaronder kleine klimmen en afdalingen worden genegeerd
+dialog.settimezone.intro=Selecteer de tijdzone waarin tijdinformatie van punten wordt afgebeeld
+dialog.settimezone.system=Gebruik tijdzone vna het systeem
+dialog.settimezone.custom=Gebruik de volgende tijdzone
+dialog.settimezone.list.toomany=Te veel keuze
+dialog.settimezone.selectedzone=Geselecteerde tijdzone
+dialog.settimezone.offsetfromutc=Verschil ten opzichte van UTC
 dialog.autoplay.duration=Duur (sec.)
 dialog.autoplay.usetimestamps=Gebruik tijdinfo van punt
 dialog.autoplay.rewind=Terug naar begin
 dialog.autoplay.pause=Pauze
 dialog.autoplay.play=Afspelen
+
 # 3d window
 dialog.3d.title=GpsPrune in 3D
 dialog.3d.altitudefactor=Hoogte overdrijvingsfactor
@@ -805,13 +818,12 @@ units.degreesfahrenheit.short=\u00baF
 logic.and=en
 logic.or=of
 
-# External urls
+# External urls and services
 url.googlemaps=maps.google.nl
 wikipedia.lang=nl
 openweathermap.lang=nl
 webservice.peakfinder=Open Peakfinder.org
 webservice.geohack=Open Geohack pagina
-webservice.panoramio=Open Panoramio kaart
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -869,7 +881,6 @@ error.jpegload.dialogtitle=Fout bij inlezen foto's
 error.jpegload.nofilesfound=Bestanden niet gevonden
 error.jpegload.nojpegsfound=Geen jpeg-bestanden gevonden
 error.jpegload.nogpsfound=Geen GPS informatie gevonden
-error.jpegload.exifreadfailed=Kon geen Exif informatie inlezen. Exif informatie kan niet worden gelezen\n zonder interne of externe bibliotheek.
 error.audioload.nofilesfound=Geen audiobestanden gevonden
 error.gpsload.unknown=Onbekende fout
 error.undofailed.title=Terugdraaien mislukt
diff --git a/tim/prune/lang/prune-texts_no.properties b/tim/prune/lang/prune-texts_no.properties
new file mode 100644 (file)
index 0000000..e5a89f6
--- /dev/null
@@ -0,0 +1,140 @@
+# Text entries for the GpsPrune application
+# Norwegian entries
+
+# Menu entries
+menu.file=Fil
+menu.file.addphotos=Legg til bilder
+menu.file.recentfiles=Nyeste filer
+menu.file.save=Lagre som tekst
+menu.file.exit=Avslutt
+menu.online=
+menu.track=
+menu.track.undo=Angre
+menu.track.clearundo=Nullstill angreliste
+menu.track.markrectangle=Marker punkter i rektangel
+function.deletemarked=Slett markerte punkter
+menu.range=Intervall
+menu.range.all=Velg alle
+menu.range.none=Velg ingen
+menu.range.start=Velg intervallets startpunkt
+menu.range.end=Velg intervallets slutpunkt
+menu.range.average=Nytt punkt midt i intervallet
+menu.range.reverse=Reverser intervallet
+menu.range.mergetracksegments=Sl\u00e5 sammen delintervaller
+menu.range.cutandmove=Kutt av og flytt valgt intervall
+menu.point=Punkt
+menu.point.editpoint=Rediger punkt
+menu.point.deletepoint=Slett punkt
+menu.photo=Bilde
+menu.photo.saveexif=Lagre Exif-data
+menu.audio=Lyd
+menu.view=Visning
+menu.view.showsidebars=Vis sidefelter
+menu.view.browser=Vis kart i nettleser
+menu.view.browser.google=Google kart
+menu.view.browser.openstreetmap=Openstreetmap
+menu.view.browser.mapquest=Mapquest
+menu.view.browser.yahoo=Yahoo maps
+menu.view.browser.bing=Bing maps
+menu.settings=Innstillinger
+menu.settings.onlinemode=Last kart fra Internet
+menu.settings.autosave=Lagre innstillinger automatisk ved avslutning
+menu.help=Hjelp
+# Popup menu for map
+menu.map.zoomin=Zoom inn
+menu.map.zoomout=Zoom ut
+menu.map.zoomfull=Zoom til hele sporet
+menu.map.newpoint=Lag nytt punkt
+menu.map.drawpoints=Lag en serie punkter
+menu.map.connect=Knyt sporpunkter til linje
+menu.map.autopan=Autosentrering
+menu.map.showmap=Vis kart
+menu.map.showscalebar=Vis m\u00e5lestokk
+menu.map.editmode=Redigeringsmodus
+
+# Alt keys for menus
+altkey.menu.file=F
+altkey.menu.online=
+altkey.menu.track=S
+altkey.menu.range=I
+altkey.menu.point=P
+altkey.menu.view=V
+altkey.menu.photo=B
+altkey.menu.audio=L
+altkey.menu.settings=n
+altkey.menu.help=H
+
+# Ctrl shortcuts for menu items
+shortcut.menu.file.open=\u00c5
+shortcut.menu.file.load=
+shortcut.menu.file.save=L
+shortcut.menu.track.undo=g
+shortcut.menu.edit.compress=K
+shortcut.menu.range.all=A
+shortcut.menu.help.help=H
+
+# Functions
+function.open=\u00c5pne fil
+function.importwithgpsbabel=Importer fil med GPSBabel
+function.loadfromgps=Hent data fra GPS
+function.sendtogps=Overf\u00f8r data til GPS
+function.exportkml=Eksporter KML
+function.exportgpx=Eksporter GPX
+function.exportpov=Eksporter POV
+function.exportimage=
+function.editwaypointname=Endre waypoint-navn
+function.compress=Komprimer spor
+function.deleterange=Fjern valgt intervall
+function.croptrack=Beskj\u00e6r sporet
+function.interpolate=Interpoler punkter
+function.deletebydate=
+function.addtimeoffset=Legg til tidsinkrement
+function.addaltitudeoffset=Legg til h\u00f8ideinkrement
+function.rearrangewaypoints=
+function.convertnamestotimes=Les waypoint-navn som tidspunkter
+function.deletefieldvalues=Slett feltets verdier
+function.findwaypoint=Finn waypoint
+function.pastecoordinates=Legg til nye koordinater
+function.charts=Grafer
+function.show3d=3-D visning
+function.distances=Avstander
+function.fullrangedetails=Vis alle detaljer
+function.estimatetime=
+function.learnestimationparams=
+function.setmapbg=Velg grunnlagskart
+function.setpaths=Angi plassering av programmer
+function.selectsegment=
+function.splitsegments=
+function.sewsegments=
+function.getgpsies=Vis spor fra gpsies.com
+function.uploadgpsies=Last opp spor til gpsies.com
+function.lookupsrtm=Hent h\u00f8yde fra SRTM
+function.downloadsrtm=
+function.getwikipedia=Vis Wikipedia info for omegn
+function.searchwikipedianames=S\u00f8k Wikipedia
+function.searchopencachingde=
+function.downloadosm=Last ned OSM data for omr\u00e5det
+function.duplicatepoint=Dupliser punkt
+function.setcolours=Velg farger
+function.setlanguage=Velg spr\u00e5k
+function.connecttopoint=Knytt til punkt
+function.disconnectfrompoint=Koble fra punkt
+function.removephoto=Fjern foto
+function.correlatephotos=Relater alle bilder
+function.rearrangephotos=Omarranger bilder
+function.rotatephotoleft=Roter bilde mot venstre
+function.rotatephotoright=Roter bilde mot h\u00f8yre
+function.photopopup=Vis bilde popup
+function.ignoreexifthumb=Ignorer exif miniatyrbilder
+function.loadaudio=Legg til lydklipp
+function.removeaudio=Fjern lydklipp
+function.correlateaudios=Relater alle lydklipp
+function.playaudio=Spill lydklipp
+function.stopaudio=Avbryt lydklipp
+function.help=Hjelp
+function.showkeys=Vis hurtigtaster
+function.about=Om GpsPrune
+function.checkversion=Sjekk etter nye versjoner
+function.saveconfig=Lagre innstillinger
+function.diskcache=Om lagring av kart
+function.managetilecache=H\u00e5ndter kart-flis-lager
index 405c594ea75e32270d9410402783d29717e55458..fb73385584038005ec84876a0ae3c777d8aab336 100644 (file)
@@ -38,7 +38,7 @@ menu.view.browser.yahoo=Mapy Yahoo
 menu.view.browser.bing=Mapy Bing
 menu.settings=Ustawienia
 menu.settings.onlinemode=\u0141aduj mapy z sieci
-menu.settings.antialias=U\u017Cyj antyaliasingu
+dialog.displaysettings.antialias=U\u017Cyj antyaliasingu
 menu.settings.autosave=Autozapis ustawie\u0144 przy wyj\u015bciu
 menu.help=Pomoc
 # Popup menu for map
@@ -82,7 +82,6 @@ function.sendtogps=Wy\u015blij dane do urz\u0105dzenia GPS
 function.exportkml=Eksportuj jako KML
 function.exportgpx=Eksportuj jako GPX
 function.exportpov=Eksportuj jako POV
-function.exportsvg=Eksportuj jako SVG
 function.exportimage=Eksportuj jako obraz
 function.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015bredniego
 function.compress=Kompresuj \u015bcie\u017ck\u0119
@@ -104,12 +103,13 @@ function.distances=Odleg\u0142o\u015bci
 function.fullrangedetails=Wszystkie detale
 function.estimatetime=Przewidywany czas
 function.learnestimationparams=Skoryguj wsp\u00f3\u0142czynniki szacowania czasu
-function.autoplay=Gra\u0107 \u015bcie\u017ck\u0119 
+function.autoplay=Gra\u0107 \u015bcie\u017ck\u0119
 function.setmapbg=Wybierz map\u0119 t\u0142a
 function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
 function.selectsegment=Wybierz bie\u017c\u0105cy fragment
 function.splitsegments=Podziel \u015bcie\u017ck\u0119 na fragmenty
 function.sewsegments=Po\u0142\u0105cz fragmenty
+function.createmarkerwaypoints=Stw\u00f3rz markery podzia\u0142u
 function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
 function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
 function.lookupsrtm=Pobierz wysoko\u015bci z SRTM
@@ -121,7 +121,6 @@ function.mapillary=Szukaj zdj\u0119cia w Mapillary
 function.downloadosm=Za\u0142aduj dane obszaru z OSM
 function.duplicatepoint=Duplikuj plik
 function.setcolours=Ustaw kolory
-function.setlinewidth=Ustaw szeroko\u015b\u0107 linii
 function.setlanguage=Zmie\u0144 j\u0119zyk
 function.connecttopoint=Przy\u0142\u0105cz do punktu
 function.disconnectfrompoint=Od\u0142\u0105cz od punktu
@@ -264,10 +263,6 @@ dialog.baseimage.zoom=Poziom zbli\u017cenia
 dialog.baseimage.incomplete=Obraz niekompletny
 dialog.baseimage.tiles=Kafelki
 dialog.baseimage.size=Rozmiar obrazu
-dialog.exportsvg.text=Wybierz parametry eksportu do pliku SVG
-dialog.exportsvg.phi=azymut \u03d5
-dialog.exportsvg.theta=K\u0105t wzniesienia \u03b8
-dialog.exportsvg.gradients=U\u017cyj gradientu jako wype\u0142nienia
 dialog.exportimage.noimagepossible=Obrazy map musz\u0105 zosta\u0107 zapisane na dysku przed ich eksportem
 dialog.exportimage.drawtrack=Rysuj \u015bcie\u017ck\u0119 na mapie
 dialog.exportimage.drawtrackpoints=Rysuj punkty
@@ -398,8 +393,8 @@ dialog.correlate.select.photolater=P\u00f3\u017aniejsze zdj\u0119cie
 dialog.correlate.options.intro=Wybierz opcje dla automatycznej korelacji
 dialog.correlate.options.offsetpanel=Przesuni\u0119cie czasowe
 dialog.correlate.options.offset=Przesuni\u0119cie
-dialog.correlate.options.offset.hours=godzin,
-dialog.correlate.options.offset.minutes=minut i
+dialog.correlate.options.offset.hours=godziny,
+dialog.correlate.options.offset.minutes=minuty i
 dialog.correlate.options.offset.seconds=sekund
 dialog.correlate.options.photolater=Zdj\u0119cie p\u00f3\u017aniejsze ni\u017c punkt
 dialog.correlate.options.pointlaterphoto=Punkt p\u00f3\u017aniejszy ni\u017c zdj\u0119cie
@@ -446,12 +441,12 @@ dialog.deletemarked.nonefound=Nie mo\u017cna usun\u0105\u0107 \u017cadnych punkt
 dialog.pastecoordinates.desc=Wprowad\u017a lub wklej wsp\u00f3\u0142rz\u0119dne
 dialog.pastecoordinates.coords=Wsp\u00f3\u0142rz\u0119dne
 dialog.pastecoordinates.nothingfound=Sprawd\u017a wsp\u00f3\u0142rz\u0119dne i spr\u00f3buj jeszcze raz
-dialog.help.help=Na stronie\nhttp://gpsprune.activityworkshop.net/\nznajdziesz wi\u0119cej informacji i porad\noraz mo\u017cliwo\u015b\u0107 kupna nowego podr\u0119cznika u\u017cytkownika w formacie PDF.
+dialog.help.help=Na stronie\nhttp://gpsprune.activityworkshop.net/\nznajdziesz wi\u0119cej informacji i porad\noraz mo\u017cliwo\u015b\u0107 kupna podr\u0119cznika u\u017cytkownika w formacie PDF.
 dialog.about.version=Wersja
 dialog.about.build=Build
 dialog.about.summarytext1=GpsPrune s\u0142u\u017cy do pobierania, wy\u015bwietlania i edycji danych z odbiornik\u00f3w GPS.
 dialog.about.summarytext2=Ten program zosta\u0142 udost\u0119pniony na podstawie licencji GNU pozwalaj\u0105cej<br>na jego wolne, nieograniczone i og\u00f3lno\u015bwiatowe u\u017cytkowanie i rozszerzanie. <br>Kopiowanie, rozprowadzanie i modyfikowanie s\u0105 dozwolone i zalecane<br>zgodnie z warunkami zawartymi w do\u0142\u0105czonym pliku<code>license.txt</code>
-dialog.about.summarytext3=Na stronie <code style="font-weight:bold">http://activityworkshop.net/</code> znajdziesz wi\u0119cej informacji i porad\noraz mo\u017cliwo\u015b\u0107 kupna nowego podr\u0119cznika u\u017cytkownika w formacie PDF.
+dialog.about.summarytext3=Na stronie <code style="font-weight:bold">https://activityworkshop.net/</code> znajdziesz wi\u0119cej informacji i porad<br>oraz mo\u017cliwo\u015b\u0107 kupna podr\u0119cznika u\u017cytkownika w formacie PDF.
 dialog.about.languages=Dost\u0119pne j\u0119zyki
 dialog.about.translatedby=Tekst po polsku: Piotr, Weehal
 dialog.about.systeminfo=Informacje o systemie
@@ -462,11 +457,6 @@ dialog.about.systeminfo.povray=Povray zainstalowany
 dialog.about.systeminfo.exiftool=Exiftool zainstalowany
 dialog.about.systeminfo.gpsbabel=Gpsbabel zainstalowany
 dialog.about.systeminfo.gnuplot=Gnuplot zainstalowany
-dialog.about.systeminfo.exiflib=Biblioteka Exif
-dialog.about.systeminfo.exiflib.internal=Wewn\u0119trzny
-dialog.about.systeminfo.exiflib.internal.failed=Wewn\u0119trzny (nie znaleziony)
-dialog.about.systeminfo.exiflib.external=Zewn\u0119trzny
-dialog.about.systeminfo.exiflib.external.failed=Zewn\u0119trzny (nie znaleziony)
 dialog.about.yes=Tak
 dialog.about.no=Nie
 dialog.about.credits=Podzi\u0119kowania
@@ -564,7 +554,7 @@ dialog.diskcache.deleteall=Usu\u0144 wszystkie p\u0142ytki
 dialog.diskcache.deleted=Usuni\u0119to %d plik\u00f3w z kesza
 dialog.deletefieldvalues.intro=Wybierz pola do skasowania z wybranego zakresu
 dialog.deletefieldvalues.nofields=Brak p\u00f3l do skasowania dla tego zakresu
-dialog.setlinewidth.text=Wprowad\u017a grubo\u015b\u0107 linii do rysowania \u015bcie\u017cek
+dialog.displaysettings.linewidth=Wprowad\u017a grubo\u015b\u0107 linii do rysowania \u015bcie\u017cek
 dialog.downloadosm.desc=Potwierd\u017a \u015bci\u0105gni\u0119cie danych dla tego obszaru z OSM:
 dialog.searchwikipedianames.search=Szukaj
 dialog.weather.location=Pozycja
@@ -597,7 +587,7 @@ dialog.deletebydate.column.delete=Usu\u0144
 dialog.setaltitudetolerance.text.metres=Limit (w metrach) poni\u017cej kt\u00f3rego, ma\u0142e spadki wzniosy b\u0119d\u0105 ignorowane
 dialog.setaltitudetolerance.text.feet=Limit (w stopach) poni\u017cej kt\u00f3rego, ma\u0142e spadki wzniosy b\u0119d\u0105 ignorowane
 dialog.autoplay.duration=Czas trwania (sek)
-dialog.autoplay.usetimestamps=U\u017Cyj znaczników czasowych
+dialog.autoplay.usetimestamps=U\u017Cyj znacznik\u00f3w czasowych
 dialog.autoplay.rewind=Przewin\u0105\u0107
 dialog.autoplay.pause=Pauza
 dialog.autoplay.play=Graj
@@ -812,7 +802,6 @@ wikipedia.lang=pl
 openweathermap.lang=pl
 webservice.peakfinder=Otw\u00f3rz w Peakfinder.org
 webservice.geohack=Otw\u00f3rz w Geohack
-webservice.panoramio=Otw\u00f3rz w Panoramio.com
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -870,7 +859,6 @@ error.jpegload.dialogtitle=B\u0142\u0105d \u0142adowania zdj\u0119cia
 error.jpegload.nofilesfound=Nie znaleziono plik\u00f3w
 error.jpegload.nojpegsfound=Nie znaleziono plik\u00f3w jpeg
 error.jpegload.nogpsfound=Nie znaleziono informacji GPS
-error.jpegload.exifreadfailed=Nie powiod\u0142o si\u0119 odczytanie informacji Exif\nInformacji tych nie mo\u017cna przeczyta\u0107 bez wewn\u0119trznej lub zewn\u0119trznej biblioteki.
 error.audioload.nofilesfound=Nie znaleziono plik\u00f3w audio
 error.gpsload.unknown=Nieznany b\u0142\u0105d
 error.undofailed.title=Cofnij nie powiod\u0142o si\u0119
index a3875d61bb8baed31ea468bd430e73108c25509c..f12eaf682b6646885f453a3e3c949061fadae646 100644 (file)
@@ -82,7 +82,6 @@ function.sendtogps=Enviar dados para o GPS
 function.exportkml=Exportar para KML
 function.exportgpx=Exportar para GPX
 function.exportpov=Exportar para POV
-function.exportsvg=Exportar para SVG
 function.exportimage=Exportar imagem
 function.editwaypointname=Editar nome do ponto
 function.compress=Comprimir rota
@@ -118,7 +117,6 @@ function.mapillary=Procurar na Mapillary.com
 function.downloadosm=Baixar dados OSM para a \u00e1rea
 function.duplicatepoint=Duplicar ponto
 function.setcolours=Definir cores
-function.setlinewidth=Definir espessura da linha
 function.setlanguage=Definir idioma
 function.connecttopoint=Conectar ao ponto
 function.disconnectfrompoint=Desconectar do ponto
@@ -260,10 +258,6 @@ dialog.baseimage.zoom=N\u00edvel de amplia\u00e7\u00e3o
 dialog.baseimage.incomplete=Imagem incompleta
 dialog.baseimage.tiles=Ladrilhos
 dialog.baseimage.size=Tamanho da imagem
-dialog.exportsvg.text=Selecione os par\u00e2metros para a exporta\u00e7\u00e3o para o SVG
-dialog.exportsvg.phi=\u00c2ngulo do azimute \u03d5
-dialog.exportsvg.theta=\u00c2ngulo da eleva\u00e7\u00e3o \u03b8
-dialog.exportsvg.gradients=Usar gradientes para sombreamento
 dialog.exportimage.noimagepossible=Imagens de mapa precisam estar em cache no disco para serem usados por uma exporta\u00e7\u00e3o.
 dialog.exportimage.drawtrack=Desenhar rota no mapa
 dialog.exportimage.drawtrackpoints=Desenhar pontos da rota
@@ -378,8 +372,10 @@ dialog.gpsies.activity.motorbiking=Motocross
 dialog.gpsies.activity.snowshoe=Snowshoeing
 dialog.gpsies.activity.sailing=Sailing
 dialog.gpsies.activity.skating=Patina\u00e7\u00e3o
+dialog.mapillary.nonefound=Nenhuma foto encontrada
 dialog.wikipedia.column.name=Nome do artigo
 dialog.wikipedia.column.distance=Dist\u00e2ncia
+dialog.wikipedia.nonefound=Nenhum artigo encontrado
 dialog.correlate.notimestamps=N\u00e3o existem data-hora nos dados dos pontos, assim n\u00e3o h\u00e1 nada para correlacionar com as fotos
 dialog.correlate.nouncorrelatedphotos=Existem fotos n\u00e3o correlacionadas.\nVoc\u00ea tem certeza de que deseja continuar?
 dialog.correlate.nouncorrelatedaudios=Existem \u00e1udios n\u00e3o correlacionados.\nVoc\u00ea tem certeza de que deseja continuar?
@@ -453,11 +449,6 @@ dialog.about.systeminfo.povray=Povray instalado
 dialog.about.systeminfo.exiftool=Exittool instalado
 dialog.about.systeminfo.gpsbabel=Gpsbabel instalado
 dialog.about.systeminfo.gnuplot=Gnuplot instalado
-dialog.about.systeminfo.exiflib=Biblioteca do Exif
-dialog.about.systeminfo.exiflib.internal=Interna
-dialog.about.systeminfo.exiflib.internal.failed=Interna (n\u00e3o encontrada)
-dialog.about.systeminfo.exiflib.external=Externa
-dialog.about.systeminfo.exiflib.external.failed=Externa (n\u00e3o encontrada)
 dialog.about.yes=Sim
 dialog.about.no=N\u00e3o
 dialog.about.credits=Cr\u00e9ditos
@@ -542,7 +533,7 @@ dialog.diskcache.deleteall=Apagar todos os fundos
 dialog.diskcache.deleted=Removidos %d arquivos do cache
 dialog.deletefieldvalues.intro=Selecione o campo a remover para o intervalo atual
 dialog.deletefieldvalues.nofields=N\u00e3o existem campos a remover para este intervalo
-dialog.setlinewidth.text=Insira a espessura das linhas para desenhar as rotas (1-4)
+dialog.displaysettings.linewidth=Espessura das linhas para desenhar as rotas (1-4)
 dialog.downloadosm.desc=Confirmar a transfer\u00eancia de dados OSM brutos para a \u00e1rea especificada:
 dialog.searchwikipedianames.search=Procurar por:
 dialog.weather.location=Localiza\u00e7\u00e3o
@@ -785,7 +776,6 @@ wikipedia.lang=pt
 openweathermap.lang=pt
 webservice.peakfinder=Abrir Peakfinder.org
 webservice.geohack=Abrir p\u00e1gina Geohack
-webservice.panoramio=Abrir mapa do Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -843,7 +833,6 @@ error.jpegload.dialogtitle=Erro ao carregar fotos
 error.jpegload.nofilesfound=Nenhum arquivo encontrado
 error.jpegload.nojpegsfound=Nenhum arquivo jpeg encontrado
 error.jpegload.nogpsfound=Nenhuma informa\u00e7\u00e3o de GPS encontrada
-error.jpegload.exifreadfailed=Falha ao ler informa\u00e7\u00f5es do Exif. Nenhuma informa\u00e7\u00e3o do Exif pode ser lida\nseja na biblioteca interna, seja na externa.
 error.audioload.nofilesfound=Nenhum arquivo de \u00e1udio encontrado
 error.gpsload.unknown=Erro desconhecido
 error.undofailed.title=Falha ao desfazer
index 46e0c58e59922548c1b8cf13099d8c492cdc76a2..b920ed284429a07a068936810e7c379852a08e8f 100644 (file)
@@ -38,7 +38,7 @@ menu.view.browser.yahoo=Harta Yahoo
 menu.view.browser.bing=Harta Bing
 menu.settings=Set\u0103ri
 menu.settings.onlinemode=\u00cencarc\u0103 h\u0103r\u021bi
-menu.settings.antialias=Folose\u0219te antialiasing
+dialog.displaysettings.antialias=Folose\u0219te antialiasing
 menu.settings.autosave=Salveaz\u0103 set\u0103rile automat la ie\u0219ire
 menu.help=Ajutor
 
@@ -83,7 +83,6 @@ function.sendtogps=Trimite date spre GPS
 function.exportkml=Export\u0103 \u00een fi\u015fier KML
 function.exportgpx=Export\u0103 \u00een fi\u015fier GPX
 function.exportpov=Export\u0103 \u00een fi\u015fier POV
-function.exportsvg=Export\u0103 \u00een fi\u015fier SVG
 function.exportimage=Export\u0103 imagine
 function.editwaypointname=Editeaz\u0103 nume waypoint
 function.compress=Comprim\u0103 traseu
@@ -122,7 +121,6 @@ function.mapillary=Caut\u0103 fotografii \u00een Mapillary
 function.downloadosm=Descarc\u0103 date OSM pentru zona traseului
 function.duplicatepoint=Duplic\u0103 punctul
 function.setcolours=Seteaz\u0103 culori
-function.setlinewidth=Seteaz\u0103 grosime linie
 function.setlanguage=Seteaz\u0103 limb\u0103
 function.connecttopoint=Conecteaz\u0103 la punct
 function.disconnectfrompoint=Deconecteaz\u0103 de la punct
@@ -265,10 +263,6 @@ dialog.baseimage.zoom=Nivel de zoom
 dialog.baseimage.incomplete=Imagine incomplet\u0103
 dialog.baseimage.tiles=\u021aigle
 dialog.baseimage.size=Dimensiunea imaginii
-dialog.exportsvg.text=Alege\u021bi parametrii pentru export SVG
-dialog.exportsvg.phi=Azimut \u03d5
-dialog.exportsvg.theta=\u00cenclina\u0163ie \u03b8
-dialog.exportsvg.gradients=Folose\u0219te gradien\u021bi pentru umbrire
 dialog.exportimage.noimagepossible=Imaginile hart\u0103 trebuie sa fie salvate \u00een cache pe disc pentru a putea fi folosite la export.
 dialog.exportimage.drawtrack=Deseneaz\u0103 traseu pe hart\u0103
 dialog.exportimage.drawtrackpoints=Deseneaz\u0103 punctele traseului
@@ -463,11 +457,6 @@ dialog.about.systeminfo.povray=Povray instalat
 dialog.about.systeminfo.exiftool=Exiftool instalat
 dialog.about.systeminfo.gpsbabel=Gpsbabel instalat
 dialog.about.systeminfo.gnuplot=Gnuplot instalat
-dialog.about.systeminfo.exiflib=Bibliotec\u0103 Exif
-dialog.about.systeminfo.exiflib.internal=Intern
-dialog.about.systeminfo.exiflib.internal.failed=Intern (absent)
-dialog.about.systeminfo.exiflib.external=Extern
-dialog.about.systeminfo.exiflib.external.failed=Extern (absent)
 dialog.about.yes=Da
 dialog.about.no=Nu
 dialog.about.credits=Mul\u021bumiri
@@ -565,7 +554,7 @@ dialog.diskcache.deleteall=\u015eterge toate imaginile
 dialog.diskcache.deleted=Au fost \u0219terse %d fi\u0219iere din cache
 dialog.deletefieldvalues.intro=Alege\u021bi c\u00e2mpurile ce urmeaz\u0103 a fi \u0219terse din intervalul curent
 dialog.deletefieldvalues.nofields=Nu poate fi \u0219ters nici un c\u00e2mp pentru intervalul dat
-dialog.setlinewidth.text=Introduce\u021bi grosimea liniilor ce vor fi desenate pentru trasee (1-3)
+dialog.displaysettings.linewidth=Introduce\u021bi grosimea liniilor ce vor fi desenate pentru trasee (1-3)
 dialog.downloadosm.desc=Confirma\u021bi desc\u0103rcarea datelor brute OSM pentru zona specificat\u0103:
 dialog.searchwikipedianames.search=Caut\u0103:
 dialog.weather.location=Loca\u0163ie
@@ -813,7 +802,6 @@ wikipedia.lang=ro
 openweathermap.lang=ro
 webservice.peakfinder=Deschide Peakfinder.org
 webservice.geohack=Deschide pagina Geohack
-webservice.panoramio=Deschide harta Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=N
@@ -871,7 +859,6 @@ error.jpegload.dialogtitle=Eroare la \u00eenc\u0103rcarea fotografiilor
 error.jpegload.nofilesfound=Nu a fost g\u0103sit niciun fi\u0219ier
 error.jpegload.nojpegsfound=Nu a fost g\u0103sit niciun fi\u0219ier jpeg
 error.jpegload.nogpsfound=\u00cen EXIF-ul fi\u0219ierelor JPEG nu a fost g\u0103sit\u0103 informa\u021bie GPS
-error.jpegload.exifreadfailed=Citirea informa\u021biei EXIF a e\u0219uat. Informa\u021bia EXIF nu poate fi citit\u0103 far\u0103 o bibliotec\u0103 extern\u0103 sau intern\u0103.
 error.audioload.nofilesfound=Nu a fost g\u0103sit niciun clip audio.
 error.gpsload.unknown=Eroare cu cauz\u0103 necunoscut\u0103
 error.undofailed.title=Anularea a e\u0219uat
index 6e6e8d7f67452f938c409ecf6c20e7c61fe5126a..e72acfa689b946e2f2205a1721e587672f976fcd 100644 (file)
@@ -1,17 +1,17 @@
 # Text entries for the GpsPrune application
-# Russian entries thanks to Sergey, Roman
+# Russian entries thanks to Sergey, Roman, Valerij
 
 # Menu entries
 menu.file=\u0424\u0430\u0439\u043b
 menu.file.addphotos=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0442\u043e
-menu.file.recentfiles=\u041f\u0440\u0438\u043d\u044f\u0442\u044b\u0435 \u0444\u0430\u0439\u043b\u044b
+menu.file.recentfiles=\u041d\u0435\u0434\u0430\u0432\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u044b
 menu.file.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u0442\u0435\u043a\u0441\u0442
-menu.file.exit=\u0412\u044b\u0445\u043e\u0434
+menu.file.exit=\u0412\u044b\u0439\u0442\u0438
 menu.online=\u041e\u043d\u043b\u0430\u0439\u043d
 menu.track=\u0422\u0440\u0435\u043a
 menu.track.undo=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
 menu.track.clearundo=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439
-menu.track.markrectangle=\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0432 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0435
+menu.track.markrectangle=\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0432 \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0435
 function.deletemarked=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
 menu.range=\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b
 menu.range.all=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435
@@ -20,7 +20,7 @@ menu.range.start=\u041d\u0430\u0447\u0430\u043b\u043e \u0438\u043d\u0442\u0435\u
 menu.range.end=\u041a\u043e\u043d\u0435\u0446 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430
 menu.range.average=\u0422\u043e\u0447\u043a\u0430 \u043f\u043e \u0441\u0440\u0435\u0434\u043d\u0435\u043c\u0443
 menu.range.reverse=\u041f\u0435\u0440\u0435\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b
-menu.range.mergetracksegments=\u0421\u043b\u0438\u0442\u044c \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u044b \u0442\u0440\u0435\u043a\u0430
+menu.range.mergetracksegments=\u0421\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u044b \u0442\u0440\u0435\u043a\u0430
 menu.range.cutandmove=\u0412\u044b\u0440\u0435\u0437\u0430\u0442\u044c \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u044b\u0431\u043e\u0440\u043a\u0443
 menu.point=\u0422\u043e\u0447\u043a\u0430
 menu.point.editpoint=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443
@@ -36,19 +36,19 @@ menu.view.browser.openstreetmap=Openstreetmap
 menu.view.browser.mapquest=Mapquest
 menu.view.browser.yahoo=Yahoo maps
 menu.view.browser.bing=Bing maps
-menu.settings=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438
+menu.settings=\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
 menu.settings.onlinemode=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u0443 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430
-menu.settings.antialias=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u0435
 menu.settings.autosave=\u0410\u0432\u0442\u043e\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435
 menu.help=\u041f\u043e\u043c\u043e\u0449\u044c
+
 # Popup menu for map
 menu.map.zoomin=\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c
 menu.map.zoomout=\u0423\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c
 menu.map.zoomfull=\u0423\u0432\u0435\u043b\u0447\u0438\u0442\u044c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e
-menu.map.newpoint=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0434\u043d\u0443 \u0442\u043e\u0447\u043a\u0443
+menu.map.newpoint=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0442\u043e\u0447\u043a\u0443
 menu.map.drawpoints=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0447\u0435\u043a
 menu.map.connect=\u0421\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0442\u0440\u0435\u043a\u0430 \u0441 \u043b\u0438\u043d\u0438\u0435\u0439
-menu.map.autopan=\u041e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435
+menu.map.autopan=\u0410\u0432\u0442\u043e\u0444\u043e\u043a\u0443\u0441 \u043d\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435
 menu.map.showmap=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u041e\u0421\u041c-\u043a\u0430\u0440\u0442\u0443
 menu.map.showscalebar=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0448\u043a\u0430\u043b\u0443 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430
 menu.map.editmode=\u0420\u0435\u0436\u0438\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f
@@ -82,19 +82,18 @@ function.sendtogps=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0432
 function.exportkml=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 KML
 function.exportgpx=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 GPX
 function.exportpov=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 POV
-function.exportsvg=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 SVG
 function.exportimage=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u0301\u043d\u0438\u0435
 function.editwaypointname=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438
 function.compress=\u0421\u0436\u0430\u0442\u044c \u0442\u0440\u0435\u043a
-function.marklifts=\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043f\u043e\u0434\u044a\u0451\u043c\u0438\u043d\u0438\u043a \u0432 \u0433\u043e\u0440\u0443
+function.marklifts=\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043f\u043e\u0434\u044a\u0451\u043c\u043d\u0438\u043a\u0438 \u0432 \u0433\u043e\u0440\u0443
 function.deleterange=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b
 function.croptrack=\u041e\u0431\u0440\u0435\u0437\u0430\u0442\u044c \u0442\u0440\u0435\u043a
-function.interpolate=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u0447\u0435\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b
+function.interpolate=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b
 function.deletebydate=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u043f\u043e \u0434\u0430\u0442\u0435
-function.addtimeoffset=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
-function.addaltitudeoffset=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0442\u043a\u0443 \u0432\u044b\u0441\u043e\u0442\u044b
-function.rearrangewaypoints=\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044b
-function.convertnamestotimes=\u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f
+function.addtimeoffset=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
+function.addaltitudeoffset=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u044b
+function.rearrangewaypoints=\u041f\u0435\u0440\u0435\u0443\u043f\u043e\u0440\u044f\u0434\u043e\u0447\u0438\u0442\u044c \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0442\u043e\u0447\u0435\u043a \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430
+function.convertnamestotimes=\u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0432 \u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 function.deletefieldvalues=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f
 function.findwaypoint=\u041d\u0430\u0439\u0442\u0438 \u043f\u0443\u0442\u0435\u0432\u0443\u044e \u0442\u043e\u0447\u043a\u0443
 function.pastecoordinates=\u0412\u0432\u043e\u0434 \u043d\u043e\u0432\u044b\u0445 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442
@@ -102,32 +101,34 @@ function.charts=\u0413\u0440\u0430\u0444\u0438\u043a\u0438
 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.estimatetime=\u041f\u0440\u043e\u0433\u043d\u043e\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u044f
+function.estimatetime=\u0421\u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u044f
 function.learnestimationparams=\u0417\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
-function.autoplay=\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435
+function.autoplay=\u0410\u0432\u0442\u043e\u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0442\u0440\u0435\u043a\u0430
 function.setmapbg=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0443-\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0443
 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.selectsegment=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0441\u0435\u0433\u043c\u0435\u043d\u0442
 function.splitsegments=\u0420\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u044b
 function.sewsegments=\u0421\u043a\u043b\u0435\u0438\u0442\u044c \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u044b \u0442\u0440\u0435\u043a\u0430 \u0432\u043e\u0435\u0434\u0438\u043d\u043e
+function.createmarkerwaypoints=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043c\u0430\u0440\u043a\u0435\u0440\u044b \u043f\u0443\u0442\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
 function.getgpsies=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a\u0438
 function.uploadgpsies=\u0412\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 gpsies.com
-function.lookupsrtm=\u0412\u044b\u0441\u043e\u0442\u044b \u0432 SRTM
-function.downloadsrtm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c STRM
+function.lookupsrtm=\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432\u044b\u0441\u043e\u0442\u0443 \u0438\u0437 SRTM
+function.downloadsrtm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c SRTM-\u0442\u0430\u0439\u043b\u044b
 function.getwikipedia=\u0421\u0442\u0430\u0442\u044c\u044f \u043e \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0432 \u0412\u0438\u043a\u0438
 function.searchwikipedianames=\u041f\u043e\u0438\u0441\u043a \u0441\u0442\u0430\u0442\u0435\u0439 \u0432 \u0412\u0438\u043a\u0438 \u043f\u043e \u0438\u043c\u0435\u043d\u0438
-function.searchopencachingde=\u041f\u043e\u0438\u0441\u043a OpenCaching.de
+function.searchosmpois=\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 OSM
+function.searchopencachingde=\u041f\u043e\u0438\u0441\u043a \u0442\u0430\u0439\u043d\u0438\u043a\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 OpenCaching.de
 function.mapillary=\u041f\u043e\u0438\u0441\u043a \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0438\u0445 \u0444\u043e\u0442\u043e
-function.downloadosm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c OSM \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0442\u0435\u0440\u0440\u0438\u0442\u043e\u0440\u0438\u044e
+function.downloadosm=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c OSM \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c
 function.duplicatepoint=\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u0447\u043a\u0443 \u0432 \u043a\u043e\u043d\u0435\u0446 \u0442\u0440\u0435\u043a\u0430
 function.setcolours=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0446\u0432\u0435\u0442\u0430
-function.setlinewidth=\u0417\u0430\u0434\u0430\u0442\u044c \u0448\u0438\u0440\u0438\u043d\u0443 \u043b\u0438\u043d\u0438\u0438
+function.setdisplaysettings=\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
 function.setlanguage=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u044f\u0437\u044b\u043a
 function.connecttopoint=\u041f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u0442\u044c \u043a \u0442\u043e\u0447\u043a\u0435
 function.disconnectfrompoint=\u041e\u0442\u043a\u0440\u0435\u043f\u0438\u0442\u044c \u043e\u0442 \u0442\u043e\u0447\u043a\u0438
 function.removephoto=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u043e\u0442\u043e
-function.correlatephotos=\u0421\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0444\u043e\u0442\u043e \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
-function.rearrangephotos=\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u043f\u043e \u0442\u0440\u0435\u043a\u0443
+function.correlatephotos=\u0421\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
+function.rearrangephotos=\u041f\u0435\u0440\u0435\u0443\u043f\u043e\u0440\u044f\u0434\u043e\u0447\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u043f\u043e \u0442\u0440\u0435\u043a\u0443
 function.rotatephotoleft=\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0444\u043e\u0442\u043e \u043d\u0430 90 \u0432\u043b\u0435\u0432\u043e
 function.rotatephotoright=\u041f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0444\u043e\u0442\u043e \u043d\u0430 90 \u0432\u043f\u0440\u0430\u0432\u043e
 function.photopopup=\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0444\u043e\u0442\u043e \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043e\u043a\u043d\u0435
@@ -141,19 +142,20 @@ function.help=\u041f\u043e\u043c\u043e\u0449\u044c
 function.showkeys=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0433\u043e\u0440\u044f\u0447\u0438\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u0438
 function.about=\u041e GpsPrune
 function.checkversion=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f
-function.saveconfig=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438
+function.saveconfig=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
 function.diskcache=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u044b \u043d\u0430 \u0434\u0438\u0441\u043a
-function.managetilecache=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u0435\u0448\u0435\u043c
+function.managetilecache=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u044d\u0448\u0435\u043c
 function.getweatherforecast=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043f\u0440\u043e\u0433\u043d\u043e\u0437 \u043f\u043e\u0433\u043e\u0434\u044b
-function.setaltitudetolerance=\u0417\u0430\u0434\u0430\u0442\u044c \u043e\u0433\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u044b
+function.setaltitudetolerance=\u0417\u0430\u0434\u0430\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u044b
+function.selecttimezone=\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441
 
 # Dialogs
-dialog.exit.confirm.title=\u0412\u044b\u0445\u043e\u0434
+dialog.exit.confirm.title=\u0412\u044b\u0439\u0442\u0438
 dialog.exit.confirm.text=\u0414\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b! \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
 dialog.openappend.title=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438\u043b\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445.
 dialog.openappend.text=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u043c?
 dialog.deletepoint.title=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0443
-dialog.deletepoint.deletephoto=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u0443 \u044d\u0442\u043e\u0439 \u0442\u043e\u0447\u043a\u0438?
+dialog.deletepoint.deletephoto=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u0441 \u044d\u0442\u043e\u0439 \u0442\u043e\u0447\u043a\u0438?
 dialog.deletephoto.title=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u043e\u0442\u043e
 dialog.deletephoto.deletepoint=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0443 \u0443 \u044d\u0442\u043e\u0433\u043e \u0444\u043e\u0442\u043e?
 dialog.deleteaudio.deletepoint=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0443 \u0443 \u044d\u0442\u043e\u0433\u043e \u0430\u0443\u0434\u0438\u043e \u0444\u0430\u0439\u043b\u0430?
@@ -174,6 +176,9 @@ dialog.openoptions.deliminfo.norecords=\u041d\u0435\u0442 \u0437\u0430\u043f\u04
 dialog.openoptions.altitudeunits=\u0415\u0434\u0438\u043d\u0438\u0446\u044b \u0432\u044b\u0441\u043e\u0442\u044b
 dialog.openoptions.speedunits=\u0415\u0434\u0438\u043d\u0438\u0446\u044b \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438
 dialog.openoptions.vertspeedunits=\u0415\u0434\u0438\u043d\u0438\u0446\u044b
+dialog.openoptions.vspeed.intro=\u041f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438:
+dialog.openoptions.vspeed.positiveup=\u041f\u043e\u0434\u044a\u0451\u043c\u0430
+dialog.openoptions.vspeed.positivedown=\u0421\u043d\u0438\u0436\u0435\u043d\u0438\u044f
 dialog.open.contentsdoubled=\u042d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043a\u0430\u0436\u0434\u043e\u0439 \u0442\u043e\u0447\u043a\u0435,\n\u043e\u0434\u043d\u0430 \u043a\u0430\u043a \u043f\u0443\u0442\u0435\u0432\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0438 \u043e\u0434\u043d\u0430 \u043a\u0430\u043a \u0442\u043e\u0447\u043a\u0430 \u0442\u0440\u0435\u043a\u0430
 dialog.selecttracks.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0440\u0435\u043a(-\u0438) \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f
 dialog.selecttracks.noname=\u0411\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0439
@@ -181,7 +186,7 @@ dialog.jpegload.subdirectories=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c
 dialog.jpegload.loadjpegswithoutcoords=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u0431\u0435\u0437 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442
 dialog.jpegload.loadjpegsoutsidearea=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0444\u043e\u0442\u043e \u0437\u0430 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u043c\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438
 dialog.jpegload.progress.title=\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u043e\u0442\u043e
-dialog.jpegload.progress=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430 \u043f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u0438\u0434\u0435\u0442 \u043f\u043e\u0438\u0441\u043a \u0444\u043e\u0442\u043e
+dialog.jpegload.progress=\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u0438\u0434\u0435\u0442 \u043f\u043e\u0438\u0441\u043a \u0444\u043e\u0442\u043e
 dialog.gpsload.nogpsbabel=Gpsbabel \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c?
 dialog.gpsload.device=\u0418\u043c\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430
 dialog.gpsload.format=\u0424\u043e\u0440\u043c\u0430\u0442
@@ -193,10 +198,28 @@ dialog.gpssend.sendtracks=\u041f\u043e\u0441\u043b\u0430\u0442\u044c \u0442\u044
 dialog.gpssend.trackname=\u0418\u043c\u044f \u0442\u0440\u0435\u043a\u0430
 dialog.gpsbabel.filters=\u0424\u0438\u043b\u044c\u0442\u0440\u044b
 dialog.addfilter.title=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440
+dialog.gpsbabel.filter.discard=\u041e\u0442\u0431\u0440\u043e\u0441\u0438\u0442\u044c
 dialog.gpsbabel.filter.simplify=\u0421\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c
 dialog.gpsbabel.filter.distance=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
 dialog.gpsbabel.filter.interpolate=\u0418\u043d\u0442\u0435\u0440\u043f\u043e\u043b\u044f\u0446\u0438\u044f
+dialog.gpsbabel.filter.discard.intro=\u041e\u0442\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438, \u0435\u0441\u043b\u0438
+dialog.gpsbabel.filter.discard.hdop=\u0423\u0445\u0443\u0434\u0448\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438 >
+dialog.gpsbabel.filter.discard.vdop=\u0423\u0445\u0443\u0434\u0448\u0435\u043d\u0438\u0435 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438 >
 dialog.gpsbabel.filter.discard.numsats=\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u043e\u0432 <
+dialog.gpsbabel.filter.discard.nofix=\u0422\u043e\u0447\u043a\u0430 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0439
+dialog.gpsbabel.filter.discard.unknownfix=\u0422\u043e\u0447\u043a\u0430 \u0438\u043c\u0435\u0435\u0442 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435
+dialog.gpsbabel.filter.simplify.intro=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0434\u043e
+dialog.gpsbabel.filter.simplify.maxpoints=\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a <
+dialog.gpsbabel.filter.simplify.maxerror=\u0438\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u044f <
+dialog.gpsbabel.filter.simplify.crosstrack=\u043f\u0435\u0440\u0435\u043a\u0440\u0435\u0441\u0442\u043e\u043a
+dialog.gpsbabel.filter.simplify.length=\u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0432 \u0434\u043b\u0438\u043d\u0435
+dialog.gpsbabel.filter.simplify.relative=\u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e hdop
+dialog.gpsbabel.filter.distance.intro=\u0423\u0434\u0430\u043b\u044f\u0442\u044c \u0442\u043e\u0447\u043a\u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0431\u043b\u0438\u0437\u043a\u0438 \u043a \u043b\u044e\u0431\u043e\u0439 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0442\u043e\u0447\u043a\u0435
+dialog.gpsbabel.filter.distance.distance=\u0415\u0441\u043b\u0438 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 <
+dialog.gpsbabel.filter.distance.time=\u0438 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f <
+dialog.gpsbabel.filter.interpolate.intro=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 \u0442\u0440\u0435\u043a\u0430
+dialog.gpsbabel.filter.interpolate.distance=\u0415\u0441\u043b\u0438 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 >
+dialog.gpsbabel.filter.interpolate.time=\u0438\u043b\u0438 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f >
 dialog.saveoptions.title=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u0430\u0439\u043b
 dialog.save.fieldstosave=\u041f\u043e\u043b\u044f \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435
 dialog.save.table.field=\u041f\u043e\u043b\u0435
@@ -207,7 +230,7 @@ dialog.save.coordinateunits=\u0415\u0434\u0438\u043d\u0438\u0446\u044b \u043a\u0
 dialog.save.altitudeunits=\u0415\u0434\u0438\u043d\u0438\u0446\u044b \u0432\u044b\u0441\u043e\u0442\u044b
 dialog.save.timestampformat=\u0424\u043e\u0440\u043c\u0430\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.save.overwrite.title=\u0424\u0430\u0439\u043b \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442
-dialog.save.overwrite.text=\u0424\u0430\u0439\u043b \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442. \u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c?
+dialog.save.overwrite.text=\u0424\u0430\u0439\u043b \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.\u0425\u043e\u0442\u0438\u0442\u0435 \u0435\u0433\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c?
 dialog.save.notypesselected=\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d \u0442\u0438\u043f \u0442\u043e\u0447\u0435\u043a
 dialog.exportkml.text=\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043a \u0434\u0430\u043d\u043d\u044b\u043c
 dialog.exportkml.altitude=\u0410\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0435 \u0432\u044b\u0441\u043e\u0442\u044b (\u0434\u043b\u044f \u0430\u0432\u0438\u0430\u0446\u0438\u0438)
@@ -215,9 +238,11 @@ dialog.exportkml.kmz=\u0421\u0436\u0430\u0442\u0438\u0435 \u0434\u043b\u044f kmz
 dialog.exportkml.exportimages=\u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u044d\u0441\u043a\u0438\u0437\u0430 \u0432 kmz
 dialog.exportkml.imagesize=\u0420\u0430\u0437\u043c\u0435\u0440 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
 dialog.exportkml.trackcolour=\u0426\u0432\u0435\u0442 \u0442\u0440\u0435\u043a\u0430
+dialog.exportkml.standardkml=\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 KML
+dialog.exportkml.extendedkml=\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u0439 KML \u0441 \u043c\u0435\u0442\u043a\u0430\u043c\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.exportgpx.name=\u0418\u043c\u044f
 dialog.exportgpx.desc=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435
-dialog.exportgpx.includetimestamps=\u0412\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043e\u0442\u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
+dialog.exportgpx.includetimestamps=\u0412\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.exportgpx.copysource=\u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a xml
 dialog.exportgpx.encoding=\u041a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0430
 dialog.exportgpx.encoding.system=\u0421\u0438\u0441\u0442\u0435\u043c\u043d\u0430\u044f
@@ -233,7 +258,7 @@ dialog.exportpov.tubesandwalls=\u0422\u0440\u0443\u0431\u044b \u0438 \u0441\u044
 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.3d.useterrain=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0440\u0435\u043b\u044c\u0435\u0444
 dialog.3d.terraingridsize=\u0420\u0430\u0437\u043c\u0435\u0440 \u0441\u0435\u0442\u043a\u0438
-dialog.exportpov.baseimage=\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043e\u0441\u043d\u043e\u0432\u044b(\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0438)
+dialog.exportpov.baseimage=\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0430 \u043e\u0441\u043d\u043e\u0432\u044b (\u043f\u043e\u0434\u043b\u043e\u0436\u043a\u0430)
 dialog.exportpov.cannotmakebaseimage=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u043e\u0441\u043d\u043e\u0432\u044b
 dialog.baseimage.title=\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u044b
 dialog.baseimage.useimage=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435
@@ -242,10 +267,6 @@ dialog.baseimage.zoom=\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u04
 dialog.baseimage.incomplete=\u041d\u0435 \u0432\u0441\u0435 \u0442\u0430\u0439\u043b\u044b \u0431\u044b\u043b\u0438 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 dialog.baseimage.tiles=\u0422\u0430\u0439\u043b\u044b
 dialog.baseimage.size=\u0420\u0430\u0437\u043c\u0435\u0440 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f
-dialog.exportsvg.text=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430 SVG
-dialog.exportsvg.phi=\u0410\u0437\u0438\u043c\u0443\u0442 \u03d5
-dialog.exportsvg.theta=\u0423\u0433\u043e\u043b \u03b8
-dialog.exportsvg.gradients=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0437\u0430\u0442\u0435\u043d\u0435\u043d\u0438\u044f
 dialog.exportimage.noimagepossible=\u041a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u043a\u0430\u0440\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u044b \u043d\u0430 \u0434\u0438\u0441\u043a \u0434\u043b\u044f \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0430
 dialog.exportimage.drawtrack=\u0420\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u0430 \u043a\u0430\u0440\u0442\u0435
 dialog.exportimage.drawtrackpoints=\u041e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0442\u0440\u0435\u043a\u0430
@@ -255,15 +276,15 @@ dialog.pointtype.track=\u0422\u043e\u0447\u043a\u0438 \u0442\u0440\u0435\u043a\u
 dialog.pointtype.waypoint=\u041f\u0443\u0442\u0435\u0432\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
 dialog.pointtype.photo=\u0422\u043e\u0447\u043a\u0438 \u0441 \u0444\u043e\u0442\u043e
 dialog.pointtype.audio=\u0422\u043e\u0447\u043a\u0438 \u0441\u043e \u0437\u0432\u0443\u043a\u043e\u043c
-dialog.pointtype.selection=\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435
+dialog.pointtype.selection=\u0422\u043e\u043b\u044c\u043a\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435
 dialog.confirmreversetrack.title=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0440\u0430\u0437\u0432\u043e\u0440\u043e\u0442
-dialog.confirmreversetrack.text=\u042d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u0437\u0432\u043e\u0440\u043e\u0442\u0430.\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435?
+dialog.confirmreversetrack.text=\u042d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u0437\u0432\u043e\u0440\u043e\u0442\u0430.\n\u0423\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435?
 dialog.confirmcutandmove.title=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 "\u0432\u044b\u0440\u0435\u0437\u0430\u0442\u044c \u0438 \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438"
-dialog.confirmcutandmove.text=\u042d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f.\n\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435?
+dialog.confirmcutandmove.text=\u042d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0430 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f.\n\u0423\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435?
 dialog.interpolate.parameter.text=\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u043e\u0447\u0435\u043a \u0434\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c\u0438
 dialog.interpolate.betweenwaypoints=\u0418\u043d\u0442\u0435\u0440\u043f\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0442\u043e\u0447\u043a\u0430\u043c\u0438 ?
 dialog.undo.title=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435(\u044f)
-dialog.undo.pretext=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0442\u043c\u0435\u043d\u044f\u0435\u043c\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435(\u044f)
+dialog.undo.pretext=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0442\u043c\u0435\u043d\u044f\u0435\u043c\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435(\u044f)
 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
@@ -282,7 +303,7 @@ dialog.addtimeoffset.subtract=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \
 dialog.addtimeoffset.days=\u0414\u043d\u0438
 dialog.addtimeoffset.hours=\u0427\u0430\u0441\u044b
 dialog.addtimeoffset.minutes=\u041c\u0438\u043d\u0443\u0442\u044b
-dialog.addtimeoffset.notimestamps=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f, \u043a\u0430\u043a \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0435 \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438
+dialog.addtimeoffset.notimestamps=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u044b\u0431\u043e\u0440\u043a\u0430 \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438
 dialog.findwaypoint.intro=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0443\u0442\u0435\u0432\u043e\u0439 \u0442\u043e\u0447\u043a\u0438 \u0438\u043b\u0438 \u0435\u0433\u043e \u0447\u0430\u0441\u0442\u044c
 dialog.findwaypoint.search=\u041f\u043e\u0438\u0441\u043a
 dialog.saveexif.title=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c Exif
@@ -306,7 +327,7 @@ dialog.charts.svgwidth=\u0448\u0438\u0440\u0438\u043d\u0430 SVG
 dialog.charts.svgheight=\u0432\u044b\u0441\u043e\u0442\u0430 SVG
 dialog.charts.needaltitudeortimes=\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u0432 \u0442\u0440\u0435\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438\u043b\u0438 \u0432\u044b\u0441\u043e\u0442\u0435
 dialog.charts.gnuplotnotfound=\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 gnuplot \u043f\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u0443\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430
-dialog.distances.intro=\u041a\u0440\u043e\u0442\u0447\u0430\u0439\u0448\u0435\u0435 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0442\u043e\u0447\u043a\u0430\u043c\u0438
+dialog.distances.intro=\u041a\u0440\u0430\u0442\u0447\u0430\u0439\u0448\u0435\u0435 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0442\u043e\u0447\u043a\u0430\u043c\u0438
 dialog.distances.column.from=\u041e\u0442 \u0442\u043e\u0447\u043a\u0438
 dialog.distances.column.to=\u0414\u043e \u0442\u043e\u0447\u043a\u0438
 dialog.distances.currentpoint=\u0422\u0435\u043a\u0443\u0449\u0430\u044f \u0442\u043e\u0447\u043a\u0430
@@ -324,8 +345,13 @@ dialog.estimatetime.parameters.timefor=\u0412\u0440\u0435\u043c\u044f \u0434\u04
 dialog.estimatetime.results=\u0412\u043e\u0442:
 dialog.estimatetime.results.estimatedtime=\u041f\u0440\u043e\u0433\u043d\u043e\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f
 dialog.estimatetime.results.actualtime=\u0420\u0435\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f
+dialog.estimatetime.error.nodistance=\u0420\u0430\u0441\u0447\u0435\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0442\u0440\u0435\u043a\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
 dialog.estimatetime.error.noaltitudes=\u0412\u044b\u0431\u043e\u0440\u043a\u0430 \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0445 \u043e \u0432\u044b\u0441\u043e\u0442\u0435
-dialog.learnestimationparams.averageerror=\u0421\u0440\u0435\u0434\u043d\u044f\u044f \u043e\u0448\u0438\u0431\u043a\u0430 (%)
+dialog.learnestimationparams.intro=\u042d\u0442\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0442\u0440\u0435\u043a\u0443
+dialog.learnestimationparams.averageerror=\u0421\u0440\u0435\u0434\u043d\u044f\u044f \u043f\u043e\u0433\u0440\u0435\u0448\u043d\u043e\u0441\u0442\u044c (%)
+dialog.learnestimationparams.combine=\u042d\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u0442\u0435\u043a\u0443\u0449\u0438\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438
+dialog.learnestimationparams.combinedresults=\u041a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b
+dialog.learnestimationparams.weight.100pccurrent=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f
 dialog.learnestimationparams.weight.current=\u0442\u0435\u043a\u0443\u0449\u0435\u0435
 dialog.learnestimationparams.weight.calculated=\u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0435
 dialog.learnestimationparams.weight.50pc=\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u043e\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0438 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u043d\u043e\u0433\u043e
@@ -355,9 +381,16 @@ dialog.gpsies.activity.motorbiking=\u041d\u0430 \u043c\u043e\u0442\u043e\u0446\u
 dialog.gpsies.activity.snowshoe=\u041d\u0430 \u0441\u043d\u0435\u0433\u043e\u0441\u0442\u0443\u043f\u0430\u0445
 dialog.gpsies.activity.sailing=\u041f\u0430\u0440\u0443\u0441\u043d\u044b\u0439 \u0441\u043f\u043e\u0440\u0442
 dialog.gpsies.activity.skating=\u041d\u0430 \u043a\u043e\u043d\u044c\u043a\u0430\u0445
+dialog.mapillary.nonefound=\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 dialog.wikipedia.column.name=\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u044c\u0438
 dialog.wikipedia.column.distance=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
-dialog.correlate.notimestamps=\u041d\u0435\u0442 \u043e\u0442\u043c\u0435\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0434\u043b\u044f \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0443\u0442\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0441 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f\u043c\u0438!
+dialog.wikipedia.nonefound=\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0441\u0442\u0430\u0442\u0435\u0439 \u0432 \u0412\u0438\u043a\u0438\u043f\u0435\u0434\u0438\u0438
+dialog.wikipedia.gallery=\u0413\u0430\u043b\u0435\u0440\u0435\u044f
+dialog.osmpois.column.name=\u0418\u043c\u044f
+dialog.osmpois.column.type=\u0422\u0438\u043f
+dialog.osmpois.nonefound=\u0422\u043e\u0447\u0435\u043a \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e
+dialog.geocaching.nonefound=\u0422\u0430\u0439\u043d\u0438\u043a\u043e\u0432 \u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e
+dialog.correlate.notimestamps=\u041d\u0435\u0442 \u043c\u0435\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0434\u043b\u044f \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0443\u0442\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0441 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f\u043c\u0438!
 dialog.correlate.nouncorrelatedphotos=\u041d\u0435\u0442 \u043d\u0435\u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439.\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.correlate.nouncorrelatedaudios=\u041d\u0435\u0442 \u043d\u0435\u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0435\u0439.\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.correlate.photoselect.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0435\u0451 \u043a\u0430\u043a \u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438
@@ -390,6 +423,7 @@ dialog.correlate.timestamp.end=\u041a\u043e\u043d\u0435\u0446
 dialog.correlate.audioselect.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 \u044d\u0442\u0438\u0445 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0435\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.
 dialog.correlate.select.audioname=\u0418\u043c\u044f \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438
 dialog.correlate.select.audiolater=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u044c \u043f\u043e\u0437\u0434\u043d\u0435\u0435
+dialog.rearrangewaypoints.desc=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438 \u043f\u0443\u0442\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
 dialog.rearrangephotos.desc=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438 \u0444\u043e\u0442\u043e
 dialog.rearrange.tostart=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e
 dialog.rearrange.toend=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0435\u0446
@@ -400,12 +434,12 @@ dialog.rearrange.sortbyname=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u04
 dialog.rearrange.sortbytime=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
 dialog.compress.closepoints.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0431\u043b\u0438\u0436\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
 dialog.compress.closepoints.paramdesc=\u0420\u0430\u0437\u043c\u0430\u0445
-dialog.compress.wackypoints.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 "\u0448\u0430\u043b\u044c\u043d\u044b\u0445"(\u043d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0445) \u0442\u043e\u0447\u0435\u043a
+dialog.compress.wackypoints.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 "\u0448\u0430\u043b\u044c\u043d\u044b\u0445" (\u043d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0445) \u0442\u043e\u0447\u0435\u043a
 dialog.compress.wackypoints.paramdesc=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
 dialog.compress.singletons.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 Singleton-\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432
 dialog.compress.singletons.paramdesc=\u0420\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435
 dialog.compress.duplicates.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u043e\u0432
-dialog.compress.douglaspeucker.title=\u0421\u0436\u0430\u0442\u0438\u0435 \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 Douglas-Peucker
+dialog.compress.douglaspeucker.title=\u0421\u0436\u0430\u0442\u0438\u0435 \u043f\u043e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443 \u0414\u0443\u0433\u043b\u0430\u0441\u0430-\u041f\u0435\u043a\u0435\u0440\u0430
 dialog.compress.douglaspeucker.paramdesc=\u0420\u0430\u0437\u043c\u0430\u0445
 dialog.compress.summarylabel=\u0422\u043e\u0447\u043a\u0438 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
 dialog.compress.confirm=%d \u0442\u043e\u0447\u043a\u0438 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b.\n\u0427\u0442\u043e\u0431\u044b \u0438\u0445 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043c\u0435\u043d\u044e \u0422\u0440\u0435\u043a->\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
@@ -413,15 +447,15 @@ dialog.compress.confirmnone=\u043d\u0435\u0442 \u043f\u043e\u043c\u0435\u0447\u0
 dialog.deletemarked.nonefound=\u041d\u0435\u0442 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
 dialog.pastecoordinates.desc=\u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0437\u0434\u0435\u0441\u044c
 dialog.pastecoordinates.coords=\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b
-dialog.pastecoordinates.nothingfound=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437
+dialog.pastecoordinates.nothingfound=\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437
 dialog.help.help=\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443\nhttp://gpsprune.activityworkshop.net/
 dialog.about.version=\u0412\u0435\u0440\u0441\u0438\u044f
 dialog.about.build=\u0420\u0435\u0432\u0438\u0437\u0438\u044f
 dialog.about.summarytext1=GpsPrune \u044d\u0442\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 GPS \u043f\u0440\u0438\u0435\u043c\u043d\u0438\u043a\u043e\u0432.
 dialog.about.summarytext2=\u042d\u0442\u043e \u0432\u044b\u043f\u0443\u0449\u0435\u043d\u043e \u043f\u043e\u0434 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0435\u0439 Gnu GPL \u0434\u043b\u044f \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0433\u043e, \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0433\u043e, \u0432\u0441\u0435\u043c\u0438\u0440\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f.<br> \u041a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0438 \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442\u0441\u044f \u0438 \u043f\u043e\u043e\u0449\u0440\u044f\u0435\u0442\u0441\u044f <br> \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c\u0438, \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u043c\u0438 \u0432 \u0444\u0430\u0439\u043b\u0435<code> license.txt</code>.
-dialog.about.summarytext3=\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code style="font-weight:bold">http://activityworkshop.net/</code>
+dialog.about.summarytext3=\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <code style="font-weight:bold">https://activityworkshop.net/</code>
 dialog.about.languages=\u0414\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u044f\u0437\u044b\u043a\u0438
-dialog.about.translatedby=\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439: \u0421\u0435\u0440\u0433\u0435\u0439 \u0428\u0438\u043b\u043e\u0432
+dialog.about.translatedby=\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439: \u0421\u0435\u0440\u0433\u0435\u0439 \u0428\u0438\u043b\u043e\u0432, \u0412\u0430\u043b\u0435\u0440\u0438\u0439 \u0420\u043e\u043c\u0430\u043d\u043e\u0432\u0441\u043a\u0438\u0439
 dialog.about.systeminfo=\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0435
 dialog.about.systeminfo.os=\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430
 dialog.about.systeminfo.java=Java Runtime
@@ -430,11 +464,6 @@ dialog.about.systeminfo.povray=Povray \u0443\u0441\u0442\u0430\u043d\u043e\u0432
 dialog.about.systeminfo.exiftool=ExifTool \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d
 dialog.about.systeminfo.gpsbabel=GPSBabel \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d
 dialog.about.systeminfo.gnuplot=Gnuplot \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d
-dialog.about.systeminfo.exiflib=Exif \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438
-dialog.about.systeminfo.exiflib.internal=\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439
-dialog.about.systeminfo.exiflib.internal.failed=\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 (\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d)
-dialog.about.systeminfo.exiflib.external=\u0412\u043d\u0435\u0448\u043d\u0438\u0439
-dialog.about.systeminfo.exiflib.external.failed=\u0412\u043d\u0435\u0448\u043d\u0438\u0439 (\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d)
 dialog.about.yes=\u0414\u0430
 dialog.about.no=\u041d\u0435\u0442
 dialog.about.credits=\u0423\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438
@@ -454,7 +483,7 @@ dialog.checkversion.newversion2=
 dialog.checkversion.releasedate1=\u042d\u0442\u0430 \u043d\u043e\u0432\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0431\u044b\u043b\u0430 \u0432\u044b\u043f\u0443\u0449\u0435\u043d\u0430
 dialog.checkversion.releasedate2=
 dialog.checkversion.download=\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 http://gpsprune.activityworkshop.net/download.html
-dialog.keys.intro=\u0412\u043c\u0435\u0441\u0442\u043e \u043c\u044b\u0448\u0438 \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0433\u043e\u0440\u044f\u0447\u0438\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u0438
+dialog.keys.intro=\u0412\u043c\u0435\u0441\u0442\u043e \u043c\u044b\u0448\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0433\u043e\u0440\u044f\u0447\u0438\u0435 \u043a\u043b\u0430\u0432\u0438\u0448\u0438
 dialog.keys.keylist=<table><tr><td></td><td>\u0421\u0434\u0432\u0438\u0433 \u043a\u0430\u0440\u0442\u044b \u0432\u043b\u0435\u0432\u043e, \u0432\u043f\u0440\u0430\u0432\u043e, \u0432\u0432\u0435\u0440\u0445, \u0432\u043d\u0438\u0437</td><td><tr><td>Ctrl + \u043b\u0435\u0432\u0430\u044f, \u043f\u0440\u0430\u0432\u0430\u044f \u0441\u0442\u0440\u0435\u043b\u043a\u0430</td><td>\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0443\u044e \u0438\u043b\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0442\u043e\u0447\u043a\u0443</td></tr><tr><td>Ctrl + \u0441\u0442\u0440\u0435\u043b\u043a\u0438 \u0432\u0432\u0435\u0440\u0445, \u0432\u043d\u0438\u0437 </td><td> \u0423\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0430</td></tr><tr><td>Ctrl + PgUp, PgDown</td><td>\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0439, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0441\u0435\u0433\u043c\u0435\u043d\u0442</td></tr><tr><td>Ctrl + Home, End</td><td>\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0435\u0440\u0432\u0443\u044e, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u0442\u043e\u0447\u043a\u0443</td></tr><tr><td>Del</td><td>\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0442\u043e\u0447\u043a\u0443</td></tr></table>
 dialog.keys.normalmodifier=Ctrl
 dialog.keys.macmodifier=Command
@@ -495,16 +524,29 @@ dialog.colourchooser.title=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u04
 dialog.colourchooser.red=\u041a\u0440\u0430\u0441\u043d\u044b\u0439
 dialog.colourchooser.green=\u0417\u0435\u043b\u0435\u043d\u044b\u0439
 dialog.colourchooser.blue=\u0421\u0438\u043d\u0438\u0439
+dialog.colourer.intro=\u0422\u043e\u0447\u0435\u0447\u043d\u0430\u044f \u0440\u0430\u0441\u043a\u0440\u0430\u0441\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0434\u0430\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u0430 \u0442\u0440\u0435\u043a\u043e\u0432
+dialog.colourer.type=\u0422\u0438\u043f \u043e\u043a\u0440\u0430\u0441\u043a\u0438
+dialog.colourer.type.none=\u041d\u0435\u0442
+dialog.colourer.type.byfile=\u041f\u043e \u0444\u0430\u0439\u043b\u0443
+dialog.colourer.type.bysegment=\u041f\u043e \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u0443
+dialog.colourer.type.byaltitude=\u041f\u043e \u0432\u044b\u0441\u043e\u0442\u0435
+dialog.colourer.type.byspeed=\u041f\u043e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438
+dialog.colourer.type.byvertspeed=\u041f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438
+dialog.colourer.type.bygradient=\u041f\u043e \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u0443
+dialog.colourer.type.bydate=\u041f\u043e \u0434\u0430\u0442\u0435
+dialog.colourer.start=\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439 \u0446\u0432\u0435\u0442
+dialog.colourer.end=\u041a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0446\u0432\u0435\u0442
+dialog.colourer.maxcolours=\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0446\u0432\u0435\u0442\u043e\u0432
 dialog.setlanguage.firstintro=\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0438\u0437 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u044f\u0437\u044b\u043a\u043e\u0432, <p> \u0438\u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u044f\u0437\u044b\u043a\u043e\u0432\u043e\u0439 \u0444\u0430\u0439\u043b.
-dialog.setlanguage.secondintro=\u0414\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u044f\u0437\u044b\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 <p>\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c GpsPrune .
+dialog.setlanguage.secondintro=\u0414\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u044f\u0437\u044b\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 <p>\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c GpsPrune .
 dialog.setlanguage.language=\u042f\u0437\u044b\u043a
 dialog.setlanguage.languagefile=\u042f\u0437\u044b\u043a\u043e\u0432\u043e\u0439 \u0444\u0430\u0439\u043b
 dialog.setlanguage.endmessage=\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 GpsPrune,\n\u0447\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u044f\u0437\u044b\u043a\u0430 \u0432\u0441\u0442\u0443\u043f\u0438\u043b\u043e \u0432 \u0441\u0438\u043b\u0443.
-dialog.setlanguage.endmessagewithautosave=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 GpsPrune,\n\u0447\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u044f\u0437\u044b\u043a\u0430 \u0432\u0441\u0442\u0443\u043f\u0438\u043b\u043e \u0432 \u0441\u0438\u043b\u0443.
+dialog.setlanguage.endmessagewithautosave=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 GpsPrune,\n\u0447\u0442\u043e\u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u044f\u0437\u044b\u043a\u0430 \u0432\u0441\u0442\u0443\u043f\u0438\u043b\u043e \u0432 \u0441\u0438\u043b\u0443.
 dialog.diskcache.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u044b \u043d\u0430 \u0434\u0438\u0441\u043a
-dialog.diskcache.dir=\u041f\u0430\u043f\u043a\u0430 \u043a\u0435\u0448\u0430
+dialog.diskcache.dir=\u041f\u0430\u043f\u043a\u0430 \u043a\u044d\u0448\u0430
 dialog.diskcache.createdir=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u043f\u043a\u0443
-dialog.diskcache.nocreate=\u041f\u0430\u043f\u043a\u0430 \u0434\u043b\u044f \u043a\u0435\u0448\u0430 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0430
+dialog.diskcache.nocreate=\u041f\u0430\u043f\u043a\u0430 \u0434\u043b\u044f \u043a\u044d\u0448\u0430 \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0430
 dialog.diskcache.cannotwrite=\u0422\u0430\u0439\u043b\u044b \u043a\u0430\u0440\u0442\u044b \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b \u0432 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435
 dialog.diskcache.table.path=\u041f\u0443\u0442\u044c
 dialog.diskcache.table.usedby=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f
@@ -519,7 +561,17 @@ dialog.diskcache.deleteall=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u04
 dialog.diskcache.deleted=\u0423\u0434\u0430\u043b\u0435\u043d\u043e %d \u0444\u0430\u0439\u043b\u043e\u0432 \u0438\u0437 \u043a\u044d\u0448\u0430
 dialog.deletefieldvalues.intro=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430
 dialog.deletefieldvalues.nofields=\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0430 \u043d\u0435\u0442 \u043f\u043e\u043b\u0435\u0439 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
-dialog.setlinewidth.text=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e\u043b\u0449\u0438\u043d\u0443 \u043b\u0438\u043d\u0438\u0439 \u0434\u043b\u044f \u0442\u0440\u0435\u043a\u043e\u0432 (1-4)
+dialog.displaysettings.linewidth=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u043e\u043b\u0449\u0438\u043d\u0443 \u043b\u0438\u043d\u0438\u0439 \u0434\u043b\u044f \u0442\u0440\u0435\u043a\u043e\u0432 (1-4)
+dialog.displaysettings.antialias=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0433\u043b\u0430\u0436\u0438\u0432\u0430\u043d\u0438\u0435
+dialog.displaysettings.waypointicons=\u0417\u043d\u0430\u0447\u043a\u0438 \u043f\u0443\u0442\u0435\u0432\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
+dialog.displaysettings.wpicon.default=\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e
+dialog.displaysettings.wpicon.ringpt=\u041a\u0440\u0443\u0433\u043b\u044b\u0439 \u043c\u0430\u0440\u043a\u0435\u0440
+dialog.displaysettings.wpicon.plectrum=\u041c\u0435\u0434\u0438\u0430\u0442\u043e\u0440
+dialog.displaysettings.wpicon.ring=\u041a\u0440\u0443\u0433
+dialog.displaysettings.wpicon.pin=\u0417\u0430\u043a\u043e\u043b\u043a\u0430
+dialog.displaysettings.size.small=\u041c\u0430\u043b\u044b\u0439
+dialog.displaysettings.size.medium=\u0421\u0440\u0435\u0434\u043d\u0438\u0439
+dialog.displaysettings.size.large=\u0411\u043e\u043b\u044c\u0448\u043e\u0439
 dialog.downloadosm.desc=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 OSM \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438:
 dialog.searchwikipedianames.search=\u041f\u043e\u0438\u0441\u043a \u0434\u043b\u044f:
 dialog.weather.location=\u041c\u0435\u0441\u0442\u043e
@@ -544,6 +596,21 @@ dialog.weather.wind=\u0412\u0435\u0442\u0435\u0440
 dialog.weather.temp=\u0422\u00b0
 dialog.weather.humidity=\u0412\u043b\u0430\u0436\u043d\u043e\u0441\u0442\u044c
 dialog.weather.creditnotice=\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043f\u043e openweathermap.org.
+dialog.deletebydate.onlyonedate=\u0423 \u0432\u0441\u0435\u0445 \u0442\u043e\u0447\u0435\u043a \u043e\u0434\u043d\u0430 \u0438 \u0442\u0430 \u0436\u0435 \u0434\u0430\u0442\u0430.
+dialog.deletebydate.intro=\u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0434\u0430\u0442\u044b \u0432 \u0442\u0440\u0435\u043a\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438
+dialog.deletebydate.nodate=\u041d\u0435\u0442 \u043c\u0435\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438
+dialog.deletebydate.column.keep=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c
+dialog.deletebydate.column.delete=\u0423\u0434\u0430\u043b\u0438\u0442\u044c
+dialog.setaltitudetolerance.text.metres=\u041f\u0440\u0435\u0434\u0435\u043b (\u0432 \u043c\u0435\u0442\u0440\u0430\u0445), \u043d\u0438\u0436\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0432\u043e\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0438 \u0441\u043f\u0443\u0441\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f
+dialog.setaltitudetolerance.text.feet=\u041f\u0440\u0435\u0434\u0435\u043b (\u0432 \u0444\u0443\u0442\u0430\u0445), \u043d\u0438\u0436\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0432\u043e\u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0438 \u0441\u043f\u0443\u0441\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f
+dialog.settimezone.intro=\u0417\u0434\u0435\u0441\u044c \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043c\u0435\u0442\u043e\u043a \u0442\u043e\u0447\u0435\u043a
+dialog.settimezone.system=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441
+dialog.settimezone.custom=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441:
+dialog.settimezone.list.toomany=\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430
+dialog.settimezone.selectedzone=\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0447\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441
+dialog.settimezone.offsetfromutc=\u0421\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u043f\u043e UTC
+dialog.autoplay.duration=\u0414\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c (\u0441\u0435\u043a.)
+dialog.autoplay.usetimestamps=\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u043a\u0438 \u0442\u043e\u0447\u043a\u0438
 dialog.autoplay.rewind=\u041d\u0430\u0437\u0430\u0434
 dialog.autoplay.pause=\u0414\u0435\u0301\u043b\u0430\u0442\u044c \u043f\u0430\u0301\u0443\u0437\u0443
 dialog.autoplay.play=\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438
@@ -590,11 +657,15 @@ confirm.downloadsrtm.1=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e %d
 confirm.downloadsrtm.none=\u0412\u0441\u0435 \u0444\u0430\u0439\u043b\u044b \u0443\u0436\u0435 \u0432 \u043a\u044d\u0448\u0435
 confirm.deletefieldvalues=\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u044b
 confirm.audioload=\u0424\u0430\u0439\u043b\u044b \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0435\u0439 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b
-confirm.correlateaudios.single=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u044c \u0431\u044b\u043b\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430
+confirm.correlateaudios.single=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u044c \u0431\u044b\u043b\u0430 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430
 confirm.correlateaudios.multi=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u0431\u044b\u043b\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b
 
 # Tips, shown just once when appropriate
 tip.title=\u0421\u043e\u0432\u0435\u0442
+tip.useamapcache=\u041f\u0443\u0442\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043a\u044d\u0448\u0430 \u0434\u0438\u0441\u043a\u0430 (\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 -> \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u044b \u043d\u0430 \u0434\u0438\u0441\u043a\u00bb)\n\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u0442\u0440\u0430\u0444\u0438\u043a.
+tip.learntimeparams=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0431\u043e\u043b\u0435\u0435 \u0442\u043e\u0447\u043d\u044b\u043c\u0438, \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435\n\u0422\u0440\u0435\u043a->\u0417\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0433\u043d\u043e\u0437\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438\n\u043d\u0430 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432\u0430\u043c\u0438 \u0442\u0440\u0435\u043a\u0438.
+tip.downloadsrtm=\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u044d\u0442\u043e, \u0432\u044b\u0437\u043e\u0432\u043e\u043c\n\u041e\u043d\u043b\u0430\u0439\u043d-> \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c SRTM-\u0442\u0430\u0439\u043b\u044b\n\u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0432\u0430\u0448\u0435\u043c \u043a\u044d\u0448\u0435 \u043a\u0430\u0440\u0442\u044b.
+tip.usesrtmfor3d=\u042d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0432\u044b\u0441\u043e\u0442.\n\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 SRTM, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0440\u0438\u0431\u043b\u0438\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u044b\u0441\u043e\u0442 \u0434\u043b\u044f \u0432\u0438\u0434\u0430 3D.
 tip.manuallycorrelateone=\u041f\u0440\u0438 \u0440\u0443\u0447\u043d\u043e\u043c \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043c\u0435\u0442\u043a\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438.
 
 # Buttons
@@ -607,7 +678,7 @@ button.overwrite=\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0
 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.edit=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
-button.exit=\u0412\u044b\u0445\u043e\u0434
+button.exit=\u0412\u044b\u0439\u0442\u0438
 button.close=\u0417\u0430\u043a\u0440\u044b\u0442\u044c
 button.continue=\u041f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c
 button.yes=\u0414\u0430
@@ -622,7 +693,7 @@ button.preview=\u041f\u0440\u0435\u0434\u043f\u0440\u043e\u0441\u043c\u043e\u044
 button.load=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c
 button.upload=\u0412\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c
 button.guessfields=\u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u043c\u044b\u0435 \u043f\u043e\u043b\u044f
-button.showwebpage=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0435\u0431\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443
+button.showwebpage=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443
 button.check=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430
 button.resettodefaults=\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0435
 button.browse=\u041e\u0431\u0437\u043e\u0440...
@@ -754,7 +825,6 @@ wikipedia.lang=ru
 openweathermap.lang=ru
 webservice.peakfinder=\u041e\u0442\u043a\u0440\u044b\u0442\u044c Peakfinder.org
 webservice.geohack=\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 Geohack
-webservice.panoramio=\u041e\u0442\u043a\u0440\u044b\u0442\u044c \u043a\u0430\u0440\u0442\u0443 Panoramio
 
 # Cardinals for 3d plots
 cardinal.n=\u0421\u0435\u0432\u0435\u0440
@@ -812,7 +882,6 @@ error.jpegload.dialogtitle=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u04
 error.jpegload.nofilesfound=\u0424\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.jpegload.nojpegsfound=JPEG-\u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.jpegload.nogpsfound=\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 GPS-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f
-error.jpegload.exifreadfailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e. \u041d\u0435\u0442 Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u200b\u200b\u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\n\u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0438\u043b\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a.
 error.audioload.nofilesfound=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
 error.gpsload.unknown=\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430
 error.undofailed.title=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c
@@ -822,9 +891,9 @@ error.rearrange.noop=\u041f\u0435\u0440\u0435\u0440\u0430\u0441\u043f\u0440\u043
 error.function.notavailable.title=\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430
 error.function.nojava3d=\u042d\u0442\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a Java3D.
 error.3d=\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f 3D-\u0432\u0438\u0434\u0430
-error.readme.notfound=Readme \u0444\u0430\u0439\u043b\u043d\u0435\u043d\u0430\u0439\u0434\u0435\u043d
+error.readme.notfound=Readme \u0444\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d
 error.osmimage.dialogtitle=\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043a\u0430\u0440\u0442\u044b
-error.osmimage.failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442.
+error.osmimage.failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043a\u0430\u0440\u0442\u0443. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442.
 error.language.wrongfile=\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043d\u0435 \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u044f\u0437\u044b\u043a\u043e\u0432\u043e\u0439 \u0444\u0430\u0439\u043b \u0434\u043b\u044f GpsPrune
 error.convertnamestotimes.nonames=\u041d\u0435\u0442 \u0438\u043c\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f
 error.lookupsrtm.nonefound=\u041d\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u043e\u0442\u043c\u0435\u0442\u043e\u043a \u0432\u044b\u0441\u043e\u0442 \u0434\u043b\u044f \u044d\u0442\u0438\u0445 \u0442\u043e\u0447\u0435\u043a.
@@ -836,4 +905,7 @@ error.playaudiofailed=\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u043e\u0441\u0
 error.cache.notthere=\u041f\u0430\u043f\u043a\u0430 \u043a\u044d\u0448\u0430 \u0441 \u0442\u0430\u0439\u043b\u0430\u043c\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430
 error.cache.empty=\u041f\u0430\u043f\u043a\u0430 \u043a\u044d\u0448\u0430 \u0441 \u0442\u0430\u0439\u043b\u0430\u043c\u0438 \u043f\u0443\u0441\u0442\u0430
 error.cache.cannotdelete=\u041d\u0435\u0442 \u0442\u0430\u0439\u043b\u043e\u0432, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
+error.learnestimationparams.failed=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441 \u044d\u0442\u043e\u0433\u043e \u0442\u0440\u0435\u043a\u0430.\n\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0440\u0435\u043a\u0438.
 error.tracksplit.nosplit=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0440\u0435\u043a
+error.downloadsrtm.nocache=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b.\n\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043a\u044d\u0448 \u0434\u0438\u0441\u043a\u0430.
+error.sewsegments.nothingdone=\u041d\u0435\u0442 \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u0448\u0438\u0442\u044b \u0432\u043c\u0435\u0441\u0442\u0435.\n\u0412 \u0442\u0440\u0435\u043a\u0435 \u0441\u0435\u0439\u0447\u0430\u0441 %d \u0441\u0435\u0433\u043c\u0435\u043d\u0442\u043e\u0432.
index 59a84af085fa5cf7ebb430dece7151b308dc1a45..7b227d230531edb1c2d775a93de689f3717ac35b 100644 (file)
@@ -82,7 +82,6 @@ function.sendtogps=Skicka till GPS
 function.exportkml=Exportera KML
 function.exportgpx=Exportera GPX
 function.exportpov=Exportera POV
-function.exportsvg=Exportera SVG
 function.exportimage=Exportera bildfil
 function.editwaypointname=Redigera namn p\u00e5 waypoint
 function.compress=Komprimera sp\u00e5r
index ddfb0ac7888bc2dd9d95d7f9e6b6953651882c28..212edf464a2d57a1f18c5b5d40258bddfec7355d 100644 (file)
@@ -81,7 +81,6 @@ function.sendtogps=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\
 function.exportkml=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 KML
 function.exportgpx=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 GPX
 function.exportpov=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 POV
-function.exportsvg=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0432 SVG
 function.exportimage=\u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f
 function.editwaypointname=\u0420\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u0442\u0438 \u0456\u043c\u2019\u044f \u0448\u043b\u044f\u0445\u043e\u0432\u043e\u0457 \u0442\u043e\u0447\u043a\u0438
 function.compress=\u0421\u0442\u0438\u0441\u043d\u0443\u0442\u0438 \u0442\u0440\u0435\u043a
@@ -116,7 +115,6 @@ function.searchwikipedianames=\u041f\u043e\u0448\u0443\u043a \u0441\u0442\u0430\
 function.downloadosm=\u0417\u0432\u0430\u043d\u0442\u0430\u0436\u0438\u0442\u0438 OSM-\u0434\u0430\u043d\u0456 \u043d\u0430 \u0442\u0435\u0440\u0438\u0442\u043e\u0440\u0456\u044e
 function.duplicatepoint=\u041a\u043e\u043f\u0456\u044e\u0432\u0430\u0442\u0438 \u0442\u043e\u0447\u043a\u0443 \u0432 \u043a\u0456\u043d\u0435\u0446\u044c \u0442\u0440\u0435\u043a\u0443
 function.setcolours=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043a\u043e\u043b\u044c\u043e\u0440\u0438
-function.setlinewidth=\u0417\u0430\u0434\u0430\u0442\u0438 \u0448\u0438\u0440\u0438\u043d\u0443 \u043b\u0456\u043d\u0456\u0457
 function.setlanguage=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438 \u043c\u043e\u0432\u0443
 function.connecttopoint=\u041f\u0440\u0438\u043a\u0440\u0456\u043f\u0438\u0442\u0438 \u0434\u043e \u0442\u043e\u0447\u043a\u0438
 function.disconnectfrompoint=\u0412\u0456\u0434\u043a\u0440\u0456\u043f\u0438\u0442\u0438 \u0432\u0456\u0434 \u0442\u043e\u0447\u043a\u0438
index a2344e4b5228a8eeb5e92e3019e51986dbbb8664..d1c128265f32101d256b10fec298d6fc3d989d00 100644 (file)
@@ -81,7 +81,6 @@ function.sendtogps=\u53d1\u9001\u81f3GPS
 function.exportkml=\u8f93\u51faKML\u6587\u4ef6
 function.exportgpx=\u8f93\u51faGPX\u6587\u4ef6
 function.exportpov=\u8f93\u51faPOV\u6587\u4ef6
-function.exportsvg=\u8f93\u51faSVG\u6587\u4ef6
 function.exportimage=\u8f93\u51fa\u56fe\u50cf
 function.editwaypointname=\u7f16\u8f91\u822a\u70b9\u540d
 function.compress=\u538b\u7f29\u8f68\u8ff9(\u6807\u8bb0\u8981\u5220\u9664\u822a\u70b9)
@@ -116,7 +115,6 @@ function.searchwikipedianames=\u6309\u540d\u5b57\u4ece\u7ef4\u57fa\u767e\u79d1\u
 function.downloadosm=\u4e0b\u8f7d\u6b64\u5730OSM\u6570\u636e
 function.duplicatepoint=\u590d\u5236\u70b9
 function.setcolours=\u8bbe\u7f6e\u989c\u8272
-function.setlinewidth=\u8bbe\u7f6e\u7ebf\u4f53\u5bbd\u5ea6
 function.setlanguage=\u8bbe\u7f6e\u8bed\u8a00
 function.connecttopoint=\u94fe\u63a5\u5230\u5f53\u524d\u70b9
 function.disconnectfrompoint=\u64a4\u9500\u94fe\u63a5
@@ -259,10 +257,6 @@ dialog.baseimage.zoom=\u7f29\u653e\u7ea7\u522b
 dialog.baseimage.incomplete=\u56fe\u50cf\u4e0d\u5b8c\u6574
 dialog.baseimage.tiles=\u5730\u56fe\u5757
 dialog.baseimage.size=\u56fe\u50cf\u5c3a\u5bf8
-dialog.exportsvg.text=\u9009\u62e9\u8f93\u51faSVG\u6587\u4ef6\u7684\u53c2\u6570
-dialog.exportsvg.phi=\u65b9\u4f4d\u89d2
-dialog.exportsvg.theta=\u4ef0\u89d2
-dialog.exportsvg.gradients=\u4f7f\u7528\u6e10\u53d8\u8272
 dialog.exportimage.noimagepossible=\u8f93\u51fa\u7684\u5730\u56fe\u56fe\u50cf\u9996\u5148\u9700\u8981\u7f13\u5b58\u5728\u672c\u5730\u78c1\u76d8\u4e0a
 dialog.exportimage.drawtrack=\u7ed8\u51fa\u8f68\u8ff9
 dialog.exportimage.drawtrackpoints=\u7ed8\u51fa\u5404\u8f68\u8ff9\u70b9
@@ -442,8 +436,8 @@ dialog.help.help=\u66f4\u591a\u4fe1\u606f\u548c\u7528\u6cd5\uff0c\u8bf7\u53c2\u8
 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.summarytext2=\u5b83\u7684\u53d1\u884c\u662f\u57fa\u4e8eGnu GPL\u89c4\u5219\uff0c\u662f\u514d\u8d39\u7684\uff0c\u5f00\u653e\u5f0f\u7684\uff0c\u5168\u4e16\u754c\u5171\u7528\u5e76\u6539\u5584\u589e\u5f3a\u5176\u529f\u80fd\n\u5728\u7b26\u5408"license.txt"\u6761\u4ef6\u4e0b\uff0c\u5bb9\u8bb8\u5e76\u9f13\u52b1\u590d\u5236\uff0c\u5206\u53d1\u53ca\u4fee\u6539\u3002
-dialog.about.summarytext3=\u66f4\u591a\u4fe1\u606f\u53ca\u7528\u6cd5\u6307\u5357\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\uff1a\nhttp:activityworkshop.net/
+dialog.about.summarytext2=\u5b83\u7684\u53d1\u884c\u662f\u57fa\u4e8eGnu GPL\u89c4\u5219\uff0c\u662f\u514d\u8d39\u7684\uff0c\u5f00\u653e\u5f0f\u7684\uff0c\u5168\u4e16\u754c\u5171\u7528\u5e76\u6539\u5584\u589e\u5f3a\u5176\u529f\u80fd<br>\u5728\u7b26\u5408"license.txt"\u6761\u4ef6\u4e0b\uff0c\u5bb9\u8bb8\u5e76\u9f13\u52b1\u590d\u5236\uff0c\u5206\u53d1\u53ca\u4fee\u6539\u3002
+dialog.about.summarytext3=\u66f4\u591a\u4fe1\u606f\u53ca\u7528\u6cd5\u6307\u5357\uff0c\u8bf7\u53c2\u8003\u7f51\u7ad9\uff1a<br>http:activityworkshop.net/
 dialog.about.languages=\u652f\u6301\u8bed\u8a00
 dialog.about.translatedby=\u4e2d\u6587\u7ffb\u8bd1\uff1a\u9ed1\u8001\u9648 (Sam Chen)
 dialog.about.systeminfo=\u7cfb\u7edf\u4fe1\u606f
@@ -454,11 +448,6 @@ dialog.about.systeminfo.povray=Povray \u662f\u5426\u5b89\u88c5
 dialog.about.systeminfo.exiftool=Exiftool \u662f\u5426\u5b89\u88c5
 dialog.about.systeminfo.gpsbabel=Gpsbabel \u662f\u5426\u5b89\u88c5
 dialog.about.systeminfo.gnuplot=Gnuplot \u662f\u5426\u5b89\u88c5
-dialog.about.systeminfo.exiflib=Exif \u5e93
-dialog.about.systeminfo.exiflib.internal=\u5185\u90e8
-dialog.about.systeminfo.exiflib.internal.failed=\u5185\u90e8(\u672a\u627e\u5230)
-dialog.about.systeminfo.exiflib.external=\u5916\u90e8
-dialog.about.systeminfo.exiflib.external.failed=\u5916\u90e8(\u672a\u627e\u5230)
 dialog.about.yes=\u662f
 dialog.about.no=\u5426
 dialog.about.credits=\u81f4\u8c22
@@ -556,7 +545,7 @@ dialog.diskcache.deleteall=\u5220\u9664\u6240\u6709\u5730\u56fe\u5757
 dialog.diskcache.deleted=\u5df2\u5220\u9664 %d \u7f13\u5b58\u5185\u6587\u4ef6
 dialog.deletefieldvalues.intro=\u9009\u62e9\u5f53\u524d\u8303\u56f4\u5185\u8981\u5220\u9664\u7684\u5b57\u6bb5
 dialog.deletefieldvalues.nofields=\u9009\u5b9a\u8303\u56f4\u5185\u6ca1\u6709\u8981\u5220\u9664\u7684\u5b57\u6bb5
-dialog.setlinewidth.text=\u8f93\u5165\u8f68\u8ff9\u7ebf\u5bbd\u50cf\u7d20\u503c(1-4)
+dialog.displaysettings.linewidth=\u8f93\u5165\u8f68\u8ff9\u7ebf\u5bbd\u50cf\u7d20\u503c(1-4)
 dialog.downloadosm.desc=\u786e\u8ba4\u4eceOSM\u4e0b\u8f7d\u8be5\u5730\u533a\u539f\u59cb\u6570\u636e:
 dialog.searchwikipedianames.search=\u67e5\u627e:
 dialog.weather.location=\u5730\u70b9
@@ -853,7 +842,6 @@ error.jpegload.dialogtitle=\u5bfc\u5165\u7167\u7247\u9519\u8bef
 error.jpegload.nofilesfound=\u627e\u4e0d\u5230\u6587\u4ef6
 error.jpegload.nojpegsfound=\u627e\u4e0d\u5230Jpeg\u6587\u4ef6
 error.jpegload.nogpsfound=\u627e\u4e0d\u5230GPS\u4fe1\u606f
-error.jpegload.exifreadfailed=Exif\u8bfb\u53d6\u9519\u8bef\n\u9700\u8981\u5185\u90e8\u6216\u8005\u5916\u90e8\u5e93\u624d\u80fd\u8bfb\u53d6
 error.audioload.nofilesfound=\u672a\u627e\u5230\u58f0\u97f3\u6587\u4ef6
 error.gpsload.unknown=\u672a\u77e5\u9519\u8bef
 error.undofailed.title=\u64a4\u9500\u5931\u8d25
index c76318d16bfb0be060e315a52a2a35a9eeade06e..970926ba14bcdf9d22ece3cbc6bac2d45846efdc 100644 (file)
@@ -4,7 +4,7 @@ import tim.prune.I18nManager;
 import tim.prune.data.Field;
 import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
-import tim.prune.data.Timestamp;
+import tim.prune.data.TimestampUtc;
 
 /**
  * Class to try to match data with field names,
@@ -317,7 +317,7 @@ public abstract class FieldGuesser
                {
                        // must be at least 7 characters long
                        if (inValue.length() < 7) {return false;}
-                       Timestamp stamp = new Timestamp(inValue);
+                       TimestampUtc stamp = new TimestampUtc(inValue);
                        return stamp.isValid();
                }
        }
index cf60f9ec9136da3898262f31681135eba51f05e2..52bdef232942f0c5290d2c8160dccba45ce0ef92 100644 (file)
@@ -137,7 +137,7 @@ public class FileLoader
                else if (fileExtension.equals(".jpg") || fileExtension.equals("jpeg"))
                {
                        Photo photo = JpegLoader.createPhoto(inFile);
-                       TreeSet<Photo> photoSet = new TreeSet<Photo>();
+                       TreeSet<Photo> photoSet = new TreeSet<Photo>(new MediaSorter());
                        photoSet.add(photo);
                        _app.informPhotosLoaded(photoSet);
                        _app.informNoDataLoaded(); // To trigger load of next file if any
index 672cd02d3cfe74fb32e537eb173d5377f5d239cf..3e22eba029ccb76ce341a2fffaea6327669a28f1 100644 (file)
@@ -14,15 +14,16 @@ import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.data.Altitude;
 import tim.prune.data.DataPoint;
-import tim.prune.data.Field;
 import tim.prune.data.LatLonRectangle;
 import tim.prune.data.Latitude;
 import tim.prune.data.Longitude;
 import tim.prune.data.Photo;
 import tim.prune.data.Timestamp;
+import tim.prune.data.TimestampLocal;
+import tim.prune.data.TimestampUtc;
 import tim.prune.data.UnitSetLibrary;
 import tim.prune.function.Cancellable;
-import tim.prune.jpeg.ExifGateway;
+import tim.prune.jpeg.InternalExifLibrary;
 import tim.prune.jpeg.JpegData;
 
 /**
@@ -224,7 +225,7 @@ public class JpegLoader implements Runnable, Cancellable
                // Create Photo object
                Photo photo = new Photo(inFile);
                // Try to get information out of exif
-               JpegData jpegData = ExifGateway.getJpegData(inFile);
+               JpegData jpegData = new InternalExifLibrary().getJpegData(inFile);
                Timestamp timestamp = null;
                if (jpegData != null)
                {
@@ -253,12 +254,12 @@ public class JpegLoader implements Runnable, Cancellable
                }
                // Use file timestamp if exif timestamp isn't available
                if (timestamp == null) {
-                       timestamp = new Timestamp(inFile.lastModified());
+                       timestamp = new TimestampUtc(inFile.lastModified());
                }
                // Apply timestamp to photo and its point (if any)
                photo.setTimestamp(timestamp);
                if (photo.getDataPoint() != null) {
-                       photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.Format.ISO8601), false);
+                       // photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.Format.ISO8601), false);
                }
                return photo;
        }
@@ -355,7 +356,7 @@ public class JpegLoader implements Runnable, Cancellable
                if (inDate == null || inTime == null || inDate.length != 3 || inTime.length != 3) {
                        return null;
                }
-               return new Timestamp(inDate[0], inDate[1], inDate[2],
+               return new TimestampLocal(inDate[0], inDate[1], inDate[2],
                        inTime[0], inTime[1], inTime[2]);
        }
 
@@ -370,7 +371,7 @@ public class JpegLoader implements Runnable, Cancellable
                Timestamp stamp = null;
                try
                {
-                       stamp = new Timestamp(Integer.parseInt(inStamp.substring(0, 4)),
+                       stamp = new TimestampLocal(Integer.parseInt(inStamp.substring(0, 4)),
                                Integer.parseInt(inStamp.substring(5, 7)),
                                Integer.parseInt(inStamp.substring(8, 10)),
                                Integer.parseInt(inStamp.substring(11, 13)),
index 8fd30b26303b689263d49d70cc05799129e8ead7..b4a0ca829efc6c75fe64b7c8478ff9b6f5f50c8f 100644 (file)
@@ -440,6 +440,11 @@ public class TextFileLoader
                        _vSpeedUnitsDropdown.addItem(I18nManager.getText(spUnit.getNameKey()));
                }
                vSpeedGrid.add(_vSpeedUnitsDropdown);
+               final String vSpeedLabelText = I18nManager.getText("dialog.openoptions.vspeed.intro");
+               if (!vSpeedLabelText.isEmpty()) {
+                       vSpeedGrid.add(new JLabel(vSpeedLabelText));
+                       vSpeedGrid.add(new JLabel(""));
+               }
                _vSpeedUpwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positiveup"));
                JRadioButton vSpeedDownwardsRadio = new JRadioButton(I18nManager.getText("dialog.openoptions.vspeed.positivedown"));
                ButtonGroup vSpeedDirGroup = new ButtonGroup();
index c8120c19f8f244583b1ede3312a8978907c18a29..780ecc0fd04d300a3ace77e504829d476b52ed88 100644 (file)
@@ -1,11 +1,11 @@
-GpsPrune version 18.6
-=====================
+GpsPrune version 19
+===================
 
 GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
 including format conversion, charting, 3d visualisation, audio and photo correlation, and online resource lookup.
 Full details can be found at https://gpsprune.activityworkshop.net/
 
-GpsPrune is copyright 2006-2016 activityworkshop.net and distributed under the terms of the Gnu GPL version 2.
+GpsPrune is copyright 2006-2018 activityworkshop.net and distributed under the terms of the Gnu GPL version 2.
 You may freely use the software, and may help others to freely use it too.  For further information
 on your rights and how they are protected, see the included license.txt file.
 
@@ -17,7 +17,7 @@ Running
 =======
 
 To run GpsPrune from the jar file, simply call it from a command prompt or shell:
-   java -jar gpsprune_18.6.jar
+   java -jar gpsprune_19.jar
 
 If the jar file is saved in a different directory, you will need to include the path.
 Depending on your system settings, you may be able to click or double-click on the jar file
@@ -25,49 +25,28 @@ in a file manager window to execute it.  A shortcut, menu item, alias, desktop i
 or other link can of course be made should you wish.
 
 To specify a language other than the default, use an additional parameter, eg:
-   java -jar gpsprune_18.6.jar --lang=DE
+   java -jar gpsprune_19.jar --lang=DE
 
 
-New with version 18.6
-=====================
-The following fix was made since version 18.5:
-  - Change of URL for SRTM tiles, and recognise when downloaded file is too small to be valid
-
-New with version 18.5
-=====================
-The following fixes and additions were made since version 18.4:
-  - When points have no altitudes, send them to a GPS with altitude 0 (prevents weird values when reading back)
-  - When points are not displayed on map, still colour the lines according to the selected scheme
-  - When points all have crazy values, don't hang the profile chart
-  - When images can't be fetched from the internet, don't delete expired cache images
-  - Translation updates to es, uk, ru, cz
-
-New with version 18.4
-=====================
-The following fixes and additions were made since version 18.3:
-  - Updated GPSBabel format descriptions (thanks, Juergen!)
-  - Removed calls to the retired opencaching.com site (thanks, Garmin!)
-
-New with version 18.3
-=====================
-The following fixes and additions were made since version 18.2:
-  - Fix for saving track names and descriptions containing ampersands in xml formats (thanks, Joe!)
-  - Additional window icon sizes (thanks, Sebastic!)
-  - Remember window size
-
-New with version 18.2
-=====================
-The following fixes and additions were made since version 18.1:
-  - Fix for saving point names containing ampersands in gpx, kml, kmz formats (thanks, Joe!)
-  - Minor translation updates to nl, it, pl
-
-New with version 18.1
-=====================
+New with version 19
+===================
 The following fixes and additions were made since version 18:
   - Fix for duration rounding bug affecting sub-second timestamps
   - Wikipedia search now also includes galleries from wikimedia
   - Photo popup window now gets updated when the track selection changes
-  - Small improvements to Ukrainian, Dutch translations
+  - Remove export to SVG function as it has fallen behind the java3d and pov options
+  - Remove Turkish language because there haven't been any translators since early 2010
+  - Function to add waypoints along the track at intervals of distance or time (eg every 5 km)
+  - Optionally draw arrows on the track lines to show direction of travel
+  - Waypoint rendering using icons (wishlist 71)
+  - Allow user to select timezone in which timestamps are displayed (wishlist 61)
+  - Provide call to geonames.org's OSM node search function, to find amenities (like bus stops) close to the current point
+  - Fix OSM download function using overpass API
+  - Fix opencyclemap URLs
+  - Fix reading of Exif data for some unusual cases (from Adobe Lightroom?)
+  - Remove Panoramio
+  - Update wikimedia catalogue
+  - Debian and Ubuntu packages no longer rely on external libmetadata jar
 
 New with version 18
 ===================
index 1e199d9e7bb387ee6dafdbdb903772fb4d1ff9ac..45af83e0a7cac09286848f55be8508b3d2e87c9e 100644 (file)
@@ -604,7 +604,7 @@ public class FileSaver
                        if (inPoint.hasTimestamp())
                        {
                                // format value accordingly
-                               inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
+                               inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat, null));
                        }
                }
                else if (inField == Field.MEDIA_FILENAME)
index 7cac01c8b71f1b8bf5d0040c70ed3479a93988ca..39b6a362589ce72150cdb4e8a56d85609618e92a 100644 (file)
@@ -541,7 +541,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                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(UnitSetLibrary.UNITS_METRES));
-               source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
+               source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
                if (inPoint.isWaypoint())
                {
                        source = replaceGpxTags(source, "<name>", "</name>", XmlUtils.fixCdata(inPoint.getWaypointName()));
@@ -693,7 +693,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
                {
                        inWriter.write("\t\t<time>");
-                       inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
+                       inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
                        inWriter.write("</time>\n");
                }
                // write waypoint name after elevation and time
@@ -762,7 +762,7 @@ public class GpxExporter extends GenericFunction implements Runnable
                if (inPoint.hasTimestamp() && inSettings.getExportTimestamps())
                {
                        inWriter.write("\t\t\t\t<time>");
-                       inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
+                       inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601, null));
                        inWriter.write("</time>\n");
                }
                // photo, audio
index f3383fe33c2b936c001420df545baf482a6e02dd..ea188921d8b75b4612de63cd1c9335771216d2ed 100644 (file)
@@ -40,6 +40,8 @@ import tim.prune.gui.colour.PointColourer;
 import tim.prune.gui.map.MapSource;
 import tim.prune.gui.map.MapSourceLibrary;
 import tim.prune.gui.map.MapUtils;
+import tim.prune.gui.map.WpIconDefinition;
+import tim.prune.gui.map.WpIconLibrary;
 import tim.prune.load.GenericFileFilter;
 import tim.prune.threedee.ImageDefinition;
 
@@ -331,18 +333,33 @@ public class ImageExporter extends GenericFunction implements BaseImageConsumer
                // Now the waypoints
                final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
                g.setColor(textColour);
+               WpIconDefinition wpIconDefinition = null;
+               final int wpType = Config.getConfigInt(Config.KEY_WAYPOINT_ICONS);
+               if (wpType != WpIconLibrary.WAYPT_DEFAULT)
+               {
+                       wpIconDefinition = WpIconLibrary.getIconDefinition(wpType, WpIconLibrary.SIZE_MEDIUM);
+               }
                // Loop again to draw waypoints
                for (int i=0; i<numPoints; i++)
                {
                        DataPoint point = track.getPoint(i);
                        if (point.isWaypoint())
                        {
-                               // draw blob for each waypoint
+                               // use zoom level to calculate pixel coords on image
                                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);
+                               // Fill Rect or draw icon image?
                                g.fillRect(px-3, py-3, 6, 6);
+                               if (wpIconDefinition == null)
+                               {
+                                       g.fillRect(px-3, py-3, 6, 6);
+                               }
+                               else
+                               {
+                                       g.drawImage(wpIconDefinition.getImageIcon().getImage(), px-wpIconDefinition.getXOffset(),
+                                               py-wpIconDefinition.getYOffset(), null);
+                               }
                        }
                }
                // Set text size according to input
index bb1d5a3239a238d40953d5d4f004fed76ee6b87d..9d1c0789b4f4d9a3e6820f5883b7f7235bac74ab 100644 (file)
@@ -684,7 +684,7 @@ public class KmlExporter extends GenericFunction implements Runnable
                                        // Add timestamp (if any) to the list
                                        whenList.append("<when>");
                                        if (point.hasTimestamp()) {
-                                               whenList.append(point.getTimestamp().getText(Timestamp.Format.ISO8601));
+                                               whenList.append(point.getTimestamp().getText(Timestamp.Format.ISO8601, null));
                                        }
                                        whenList.append("</when>\n");
                                        // Add coordinates to the list
index c00274787a0f458ebddadbe092070045e1a6ce7f..e0fc9d35be049168096f68d97c1b7286901ae462 100644 (file)
@@ -480,7 +480,7 @@ public class PovExporter extends Export3dFunction
        private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile, File inTerrainFile)
        throws IOException
        {
-               inWriter.write("// Pov file produced by GpsPrune - see http://gpsprune.activityworkshop.net/");
+               inWriter.write("// Pov file produced by GpsPrune - see https://gpsprune.activityworkshop.net/");
                inWriter.write(inLineSeparator);
                inWriter.write("#version 3.6;");
                inWriter.write(inLineSeparator);
@@ -864,7 +864,7 @@ public class PovExporter extends Export3dFunction
 
        /**
         * @param inCode height code to check
-        * @return validated height code within range 0 to max
+        * @return validated height code within range 0 to maxHeightCode
         */
        private static byte checkHeightCode(byte inCode)
        {
diff --git a/tim/prune/save/SvgExporter.java b/tim/prune/save/SvgExporter.java
deleted file mode 100644 (file)
index d623b4a..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-package tim.prune.save;
-
-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.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.text.NumberFormat;
-import java.util.Iterator;
-import java.util.TreeSet;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-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.JTextField;
-import javax.swing.SwingConstants;
-
-import tim.prune.App;
-import tim.prune.I18nManager;
-import tim.prune.UpdateMessageBroker;
-import tim.prune.config.Config;
-import tim.prune.data.Track;
-import tim.prune.function.Export3dFunction;
-import tim.prune.gui.DialogCloser;
-import tim.prune.load.GenericFileFilter;
-import tim.prune.threedee.ThreeDModel;
-
-/**
- * Class to export a 3d scene of the track to a specified Svg file
- */
-public class SvgExporter extends Export3dFunction
-{
-       private Track _track = null;
-       private JDialog _dialog = null;
-       private JFileChooser _fileChooser = null;
-       private double _phi = 0.0, _theta = 0.0;
-       private JTextField _phiField = null, _thetaField = null;
-       private JTextField _altitudeFactorField = null;
-       private JCheckBox _gradientsCheckbox = null;
-       private static final double _scaleFactor = 200.0;
-
-
-       /**
-        * Constructor
-        * @param inApp App object
-        */
-       public SvgExporter(App inApp)
-       {
-               super(inApp);
-               _track = inApp.getTrackInfo().getTrack();
-               // Set default rotation angles
-               _phi = 30;  _theta = 55;
-       }
-
-       /** Get the name key */
-       public String getNameKey() {
-               return "function.exportsvg";
-       }
-
-       /**
-        * Set the rotation angles using coordinates for the camera
-        * @param inX X coordinate of camera
-        * @param inY Y coordinate of camera
-        * @param inZ Z coordinate of camera
-        */
-       public void setCameraCoordinates(double inX, double inY, double inZ)
-       {
-               // Calculate phi and theta based on camera x,y,z
-               _phi = Math.toDegrees(Math.atan2(inX, inZ));
-               _theta = Math.toDegrees(Math.atan2(inY, Math.sqrt(inX*inX + inZ*inZ)));
-       }
-
-
-       /**
-        * Show the dialog to select options and export file
-        */
-       public void begin()
-       {
-               // Make dialog window to select input parameters
-               if (_dialog == null)
-               {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.getContentPane().add(makeDialogComponents());
-               }
-               // Get exaggeration factor from config
-               final int exaggFactor = Config.getConfigInt(Config.KEY_HEIGHT_EXAGGERATION);
-               if (exaggFactor > 0) {
-                       _altFactor = exaggFactor / 100.0;
-               }
-
-               // Set angles
-               NumberFormat threeDP = NumberFormat.getNumberInstance();
-               threeDP.setMaximumFractionDigits(3);
-               _phiField.setText(threeDP.format(_phi));
-               _thetaField.setText(threeDP.format(_theta));
-               // Set vertical scale
-               _altitudeFactorField.setText("" + _altFactor);
-               // Show dialog
-               _dialog.pack();
-               _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());
-               JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportsvg.text"));
-               introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
-               panel.add(introLabel, BorderLayout.NORTH);
-               // OK, Cancel buttons
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               JButton okButton = new JButton(I18nManager.getText("button.ok"));
-               okButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               doExport();
-                               _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);
-
-               // central panel
-               JPanel centralPanel = new JPanel();
-               centralPanel.setLayout(new GridLayout(0, 2, 10, 4));
-
-               // rotation angles
-               JLabel phiLabel = new JLabel(I18nManager.getText("dialog.exportsvg.phi"));
-               phiLabel.setHorizontalAlignment(SwingConstants.TRAILING);
-               centralPanel.add(phiLabel);
-               _phiField = new JTextField("" + _phi);
-               _phiField.addKeyListener(new DialogCloser(_dialog));
-               centralPanel.add(_phiField);
-               JLabel thetaLabel = new JLabel(I18nManager.getText("dialog.exportsvg.theta"));
-               thetaLabel.setHorizontalAlignment(SwingConstants.TRAILING);
-               centralPanel.add(thetaLabel);
-               _thetaField = new JTextField("" + _theta);
-               centralPanel.add(_thetaField);
-               // Altitude exaggeration
-               JLabel altFactorLabel = new JLabel(I18nManager.getText("dialog.3d.altitudefactor"));
-               altFactorLabel.setHorizontalAlignment(SwingConstants.TRAILING);
-               centralPanel.add(altFactorLabel);
-               _altitudeFactorField = new JTextField("" + _altFactor);
-               centralPanel.add(_altitudeFactorField);
-               // Checkbox for gradients or not
-               JLabel gradientsLabel = new JLabel(I18nManager.getText("dialog.exportsvg.gradients"));
-               gradientsLabel.setHorizontalAlignment(SwingConstants.TRAILING);
-               centralPanel.add(gradientsLabel);
-               _gradientsCheckbox = new JCheckBox();
-               _gradientsCheckbox.setSelected(true);
-               centralPanel.add(_gradientsCheckbox);
-
-               // add this grid 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);
-               holderPanel.add(boxPanel, BorderLayout.CENTER);
-
-               panel.add(holderPanel, BorderLayout.CENTER);
-               return panel;
-       }
-
-
-       /**
-        * Select the file and export data to it
-        */
-       private void doExport()
-       {
-               // Copy camera coordinates
-               _phi = checkAngle(_phiField.getText());
-               _theta = checkAngle(_thetaField.getText());
-
-               // OK pressed, so choose output file
-               if (_fileChooser == null)
-               {
-                       _fileChooser = new JFileChooser();
-                       _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
-                       _fileChooser.setFileFilter(new GenericFileFilter("filetype.svg", new String[] {"svg"}));
-                       _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 file = _fileChooser.getSelectedFile();
-                               if (!file.getName().toLowerCase().endsWith(".svg")) {
-                                       file = new File(file.getAbsolutePath() + ".svg");
-                               }
-                               // Check if file exists and if necessary prompt for overwrite
-                               Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
-                               if (!file.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(file))
-                                       {
-                                               // file saved - store directory in config for later
-                                               Config.setConfigString(Config.KEY_TRACK_DIR, file.getParentFile().getAbsolutePath());
-                                               // also store exaggeration
-                                               Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_altFactor * 100));
-                                       }
-                                       else {
-                                               // export failed so need to choose again
-                                               chooseAgain = true;
-                                       }
-                               }
-                               else {
-                                       // overwrite cancelled so need to choose again
-                                       chooseAgain = true;
-                               }
-                       }
-               } while (chooseAgain);
-       }
-
-
-       /**
-        * Export the track data to the specified file
-        * @param inFile File object to save to
-        * @return true if successful
-        */
-       private boolean exportFile(File inFile)
-       {
-               FileWriter writer = null;
-               // find out the line separator for this system
-               String lineSeparator = System.getProperty("line.separator");
-               try
-               {
-                       // create and scale model
-                       ThreeDModel model = new ThreeDModel(_track);
-                       try
-                       {
-                               // try to use given altitude factor
-                               _altFactor = Double.parseDouble(_altitudeFactorField.getText());
-                               model.setAltitudeFactor(_altFactor);
-                       }
-                       catch (NumberFormatException nfe) {}
-                       model.scale();
-
-                       boolean useGradients = _gradientsCheckbox.isSelected();
-
-                       // Create file and write basics
-                       writer = new FileWriter(inFile);
-                       writeStartOfFile(writer, useGradients, lineSeparator);
-                       writeBasePlane(writer, lineSeparator);
-                       // write out cardinal letters NESW
-                       writeCardinals(writer, lineSeparator);
-
-                       // write out points
-                       writeDataPoints(writer, model, useGradients, lineSeparator);
-                       writeEndOfFile(writer, lineSeparator);
-
-                       // everything worked
-                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
-                                + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
-                                + " " + inFile.getAbsolutePath());
-                       return true;
-               }
-               catch (IOException ioe)
-               {
-                       JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
-                               I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
-               }
-               finally
-               {
-                       // close file ignoring exceptions
-                       try {
-                               writer.close();
-                       }
-                       catch (Exception e) {}
-               }
-               return false;
-       }
-
-
-       /**
-        * Write the start of the Svg file
-        * @param inWriter Writer to use for writing file
-        * @param inUseGradients true to use gradients, false for flat fills
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private static void writeStartOfFile(FileWriter inWriter, boolean inUseGradients,
-               String inLineSeparator)
-       throws IOException
-       {
-               inWriter.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
-               inWriter.write(inLineSeparator);
-               inWriter.write("<!-- Svg file produced by GpsPrune - see http://activityworkshop.net/ -->");
-               inWriter.write(inLineSeparator);
-               inWriter.write("<svg width=\"800\" height=\"700\">");
-               inWriter.write(inLineSeparator);
-               if (inUseGradients)
-               {
-                       final String defs = "<defs>" +
-                               "<radialGradient id=\"wayfill\" cx=\"0.5\" cy=\"0.5\" r=\"0.5\" fx=\"0.5\" fy=\"0.5\">" +
-                               "<stop offset=\"0%\" stop-color=\"#2323aa\"/>" +
-                               "<stop offset=\"100%\" stop-color=\"#000080\"/>" +
-                               "</radialGradient>" + inLineSeparator +
-                               "<radialGradient id=\"trackfill\" cx=\"0.5\" cy=\"0.5\" r=\"0.5\" fx=\"0.5\" fy=\"0.5\">" +
-                               "<stop offset=\"0%\" stop-color=\"#23aa23\"/>" +
-                               "<stop offset=\"100%\" stop-color=\"#008000\"/>" +
-                               "</radialGradient>" +
-                               "</defs>";
-                       inWriter.write(defs);
-                       inWriter.write(inLineSeparator);
-               }
-               inWriter.write("<g inkscape:label=\"Layer 1\" inkscape:groupmode=\"layer\" id=\"layer1\">");
-               inWriter.write(inLineSeparator);
-       }
-
-       /**
-        * Write the base plane
-        * @param inWriter Writer to use for writing file
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private void writeBasePlane(FileWriter inWriter, String inLineSeparator)
-       throws IOException
-       {
-               // Use model size and camera angles to draw path for base rectangle (using 3d transform)
-               int[] coords1 = convertCoordinates(-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]
-                       + " L " + coords4[0] + "," + coords4[1] + " z";
-               inWriter.write("<path style=\"fill:#446666;stroke:#000000;\" d=\"" + corners + "\" id=\"rect1\" />");
-               inWriter.write(inLineSeparator);
-       }
-
-       /**
-        * Write the cardinal letters NESW
-        * @param inWriter Writer to use for writing file
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private void writeCardinals(FileWriter inWriter, String inLineSeparator)
-       throws IOException
-       {
-               // Use model size and camera angles to calculate positions
-               int[] coordsN = convertCoordinates(0, 1.0, 0);
-               writeCardinal(inWriter, coordsN[0], coordsN[1], "cardinal.n", inLineSeparator);
-               int[] coordsE = convertCoordinates(1.0, 0, 0);
-               writeCardinal(inWriter, coordsE[0], coordsE[1], "cardinal.e", inLineSeparator);
-               int[] coordsS = convertCoordinates(0, -1.0, 0);
-               writeCardinal(inWriter, coordsS[0], coordsS[1], "cardinal.s", inLineSeparator);
-               int[] coordsW = convertCoordinates(-1.0, 0, 0);
-               writeCardinal(inWriter, coordsW[0], coordsW[1], "cardinal.w", inLineSeparator);
-       }
-
-       /**
-        * Write a single cardinal letter
-        * @param inWriter Writer to use for writing file
-        * @param inX x coordinate
-        * @param inY y coordinate
-        * @param inKey key for string to write
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private static void writeCardinal(FileWriter inWriter, int inX, int inY, String inKey, String inLineSeparator)
-       throws IOException
-       {
-               inWriter.write("<text x=\"" + inX + "\" y=\"" + inY + "\" font-size=\"26\" fill=\"black\" " +
-                       "stroke=\"white\" stroke-width=\"0.5\">");
-               inWriter.write(I18nManager.getText(inKey));
-               inWriter.write("</text>");
-               inWriter.write(inLineSeparator);
-       }
-
-       /**
-        * Convert the given 3d coordinates into 2d coordinates by rotating and mapping
-        * @param inX x coordinate (east)
-        * @param inY y coordinate (north)
-        * @param inZ z coordinate (up)
-        * @return 2d coordinates as integer array
-        */
-       private int[] convertCoordinates(double inX, double inY, double inZ)
-       {
-               // Rotate by phi degrees around vertical axis
-               final double cosPhi = Math.cos(Math.toRadians(_phi));
-               final double sinPhi = Math.sin(Math.toRadians(_phi));
-               final double x2 = inX * cosPhi + inY * sinPhi;
-               final double y2 = inY * cosPhi - inX * sinPhi;
-               final double z2 = inZ;
-               // Rotate by theta degrees around horizontal axis
-               final double cosTheta = Math.cos(Math.toRadians(_theta));
-               final double sinTheta = Math.sin(Math.toRadians(_theta));
-               double x3 = x2;
-               double y3 = y2 * sinTheta + z2 * cosTheta;
-               // don't need to calculate z3
-               // Scale results to sensible scale for svg
-               x3 = x3 * _scaleFactor + 400;
-               y3 = -y3 * _scaleFactor + 350;
-               return new int[] {(int) x3, (int) y3};
-       }
-
-       /**
-        * Finish off the file by closing the tags
-        * @param inWriter Writer to use for writing file
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private static void writeEndOfFile(FileWriter inWriter, String inLineSeparator)
-       throws IOException
-       {
-               inWriter.write(inLineSeparator);
-               inWriter.write("</g></svg>");
-               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
-        * @param inModel model object for getting data points
-        * @param inUseGradients true to use gradients, false for flat fills
-        * @param inLineSeparator line separator to use
-        * @throws IOException on file writing error
-        */
-       private void writeDataPoints(FileWriter inWriter, ThreeDModel inModel, boolean inUseGradients,
-               String inLineSeparator)
-       throws IOException
-       {
-               final int numPoints = inModel.getNumPoints();
-               TreeSet<SvgFragment> fragments = new TreeSet<SvgFragment>();
-               for (int i=0; i<numPoints; i++)
-               {
-                       StringBuilder builder = new StringBuilder();
-                       int[] coords = convertCoordinates(inModel.getScaledHorizValue(i), inModel.getScaledVertValue(i),
-                               inModel.getScaledAltValue(i));
-                       // vertical rod (if altitude positive)
-                       if (inModel.getScaledAltValue(i) > 0.0)
-                       {
-                               int[] baseCoords = convertCoordinates(inModel.getScaledHorizValue(i), inModel.getScaledVertValue(i), 0);
-                               builder.append("<line x1=\"").append(baseCoords[0]).append("\" y1=\"").append(baseCoords[1])
-                                       .append("\" x2=\"").append(coords[0]).append("\" y2=\"").append(coords[1])
-                                       .append("\" stroke=\"gray\" stroke-width=\"3\" />");
-                               builder.append(inLineSeparator);
-                       }
-                       // ball (different according to type)
-                       if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
-                       {
-                               // waypoint ball
-                               builder.append("<circle cx=\"").append(coords[0]).append("\" cy=\"").append(coords[1])
-                                       .append("\" r=\"11\" ").append(inUseGradients?"fill=\"url(#wayfill)\"":"fill=\"blue\"")
-                                       .append(" stroke=\"green\" stroke-width=\"0.2\" />");
-                       }
-                       else
-                       {
-                               // normal track point ball
-                               builder.append("<circle cx=\"").append(coords[0]).append("\" cy=\"").append(coords[1])
-                                       .append("\" r=\"7\" ").append(inUseGradients?"fill=\"url(#trackfill)\"":"fill=\"green\"")
-                                       .append(" stroke=\"blue\" stroke-width=\"0.2\" />");
-                       }
-                       builder.append(inLineSeparator);
-                       // add to set
-                       fragments.add(new SvgFragment(builder.toString(), coords[1]));
-               }
-
-               // Iterate over the sorted set and write to file
-               Iterator<SvgFragment> iterator = fragments.iterator();
-               while (iterator.hasNext()) {
-                       inWriter.write(iterator.next().getFragment());
-               }
-       }
-
-
-       /**
-        * Check the given angle value
-        * @param inString String entered by user
-        * @return validated value
-        */
-       private static double checkAngle(String inString)
-       {
-               double value = 0.0;
-               try {
-                       value = Double.parseDouble(inString);
-               }
-               catch (Exception e) {} // ignore parse failures
-               return value;
-       }
-}
diff --git a/tim/prune/save/SvgFragment.java b/tim/prune/save/SvgFragment.java
deleted file mode 100644 (file)
index 0d9c5d4..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package tim.prune.save;
-
-/**
- * Class to enable the sorting of Svg fragments
- */
-public class SvgFragment implements Comparable<SvgFragment>
-{
-       private String _fragment = null;
-       private int _yCoord = 0;
-
-       /**
-        * Constructor
-        * @param inFragment fragment of svg source
-        * @param inYCoord y coordinate of point, for sorting
-        */
-       public SvgFragment(String inFragment, int inYCoord)
-       {
-               _fragment = inFragment;
-               _yCoord = inYCoord;
-       }
-
-       /**
-        * @return svg fragment
-        */
-       public String getFragment()
-       {
-               return _fragment;
-       }
-
-       /**
-        * Compare method
-        */
-       public int compareTo(SvgFragment inOther)
-       {
-               int ycompare = _yCoord - inOther._yCoord;
-               if (ycompare != 0) {return ycompare;}
-               return _fragment.compareTo(inOther._fragment);
-       }
-
-       /**
-        * @param inOther other fragment to compare this one with
-        * @return true if the fragments are equal
-        */
-       public boolean equals(SvgFragment inOther)
-       {
-               return _fragment.equals(inOther._fragment);
-       }
-
-       /**
-        * @param inOther other object to compare this one with
-        * @return true if the objects are equal
-        */
-       public boolean equals(Object inOther) {
-               return (inOther instanceof SvgFragment?equals((SvgFragment) inOther):false);
-       }
-}
index fdda0fcc4f53de305e4177866ea8132c09249a97..579245db62eb2d07a08d005529f234ea13fa4bb4 100644 (file)
@@ -227,17 +227,6 @@ public class Java3DWindow implements ThreeDWindow
                                }
                        }});
                panel.add(povButton);
-               // Add button for exporting svg
-               JButton svgButton = new JButton(I18nManager.getText("function.exportsvg"));
-               svgButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               if (_orbit != null) {
-                                       callbackRender(FunctionLibrary.FUNCTION_SVGEXPORT);
-                               }
-                       }});
-               panel.add(svgButton);
-
                // Close button
                JButton closeButton = new JButton(I18nManager.getText("button.close"));
                closeButton.addActionListener(new ActionListener()
@@ -614,7 +603,7 @@ public class Java3DWindow implements ThreeDWindow
                Appearance tAppearance = new Appearance();
                if (inBaseImage != null)
                {
-                       gi.setTextureCoordinateParams(1,  2); // one coord set of two dimensions
+                       gi.setTextureCoordinateParams(1, 2); // one coord set of two dimensions
                        gi.setTextureCoordinates(0, inHelper.getTextureCoordinates());
                        Texture mapImage = new TextureLoader(inBaseImage.getImage()).getTexture();
                        tAppearance.setTexture(mapImage);
@@ -641,7 +630,7 @@ public class Java3DWindow implements ThreeDWindow
 
        /**
         * Calculate the angles and call them back to the app
-        * @param inFunction function to call (either pov or svg)
+        * @param inFunction function to call for export
         */
        private void callbackRender(Export3dFunction inFunction)
        {
@@ -664,8 +653,8 @@ public class Java3DWindow implements ThreeDWindow
                // Give the settings to the rendering function
                inFunction.setCameraCoordinates(result.x, result.y, result.z);
                inFunction.setAltitudeExaggeration(_altFactor);
-               inFunction.setTerrainDefinition(_terrainDefinition); // ignored by svg, used by pov
-               inFunction.setImageDefinition(_imageDefinition);     // ignored by svg, used by pov
+               inFunction.setTerrainDefinition(_terrainDefinition);
+               inFunction.setImageDefinition(_imageDefinition);
 
                inFunction.begin();
        }
index 5238213f21c7739d43f5f0aa04dc9001554c6977..0a50699853a8ec3d4a3736b1c0bede9223be05a4 100644 (file)
@@ -11,7 +11,7 @@ public class UndoAddTimeOffset implements UndoOperation
 {
        /** Start and end indices of section */
        private int _startIndex, _endIndex;
-       /** time offset */
+       /** time offset in seconds */
        private long _timeOffset;
 
 
@@ -19,7 +19,7 @@ public class UndoAddTimeOffset implements UndoOperation
         * Constructor
         * @param inStart start index of section
         * @param inEnd end index of section
-        * @param inOffset time offset
+        * @param inOffset time offset in seconds
         */
        public UndoAddTimeOffset(int inStart, int inEnd, long inOffset)
        {
@@ -45,7 +45,7 @@ public class UndoAddTimeOffset implements UndoOperation
        public void performUndo(TrackInfo inTrackInfo) throws UndoException
        {
                // Perform the inverse operation
-               inTrackInfo.getTrack().addTimeOffset(_startIndex, _endIndex, -_timeOffset, true);
+               inTrackInfo.getTrack().addTimeOffsetSeconds(_startIndex, _endIndex, -_timeOffset, true);
                UpdateMessageBroker.informSubscribers();
        }
 }
diff --git a/tim/prune/undo/UndoAppendPoints.java b/tim/prune/undo/UndoAppendPoints.java
new file mode 100644 (file)
index 0000000..14e147b
--- /dev/null
@@ -0,0 +1,51 @@
+package tim.prune.undo;\r
+\r
+import tim.prune.I18nManager;\r
+import tim.prune.data.TrackInfo;\r
+\r
+/**\r
+ * Operation to undo an operation (such as create marker waypoints)\r
+ * in which a number of points were appended to the track\r
+ */\r
+public class UndoAppendPoints implements UndoOperation\r
+{\r
+       private int _previousTrackLength = -1;\r
+       private int _numAppended = 0;\r
+\r
+\r
+       /**\r
+        * Constructor\r
+        */\r
+       public UndoAppendPoints(int inTrackLength)\r
+       {\r
+               _previousTrackLength = inTrackLength;\r
+       }\r
+\r
+       /**\r
+        * @param inNumPoints number of points appended to track\r
+        */\r
+       public void setNumPointsAppended(int inNumPoints)\r
+       {\r
+               _numAppended = inNumPoints;\r
+       }\r
+\r
+       /**\r
+        * @return description of operation including number of points loaded\r
+        */\r
+       public String getDescription()\r
+       {\r
+               return I18nManager.getText("undo.insert") + " (" + _numAppended + ")";\r
+       }\r
+\r
+       /**\r
+        * Perform the undo operation on the given Track\r
+        * @param inTrackInfo TrackInfo object on which to perform the operation\r
+        */\r
+       public void performUndo(TrackInfo inTrackInfo) throws UndoException\r
+       {\r
+               // crop track to previous size\r
+               inTrackInfo.getTrack().cropTo(_previousTrackLength);\r
+               // clear selection\r
+               inTrackInfo.getSelection().clearAll();\r
+       }\r
+}\r