Version 20, March 2020 master upstream/master
authoractivityworkshop <mail@activityworkshop.net>
Mon, 6 Apr 2020 19:38:26 +0000 (21:38 +0200)
committeractivityworkshop <mail@activityworkshop.net>
Mon, 6 Apr 2020 19:38:26 +0000 (21:38 +0200)
107 files changed:
.gitignore [new file with mode: 0644]
README.md
buildtools/build.sh
buildtools/pom.xml
buildtools/version.properties
src/tim/prune/App.java
src/tim/prune/FunctionLibrary.java
src/tim/prune/GpsPrune.java
src/tim/prune/I18nManager.java
src/tim/prune/config/Config.java
src/tim/prune/copyright.txt
src/tim/prune/data/Coordinate.java
src/tim/prune/data/FileInfo.java
src/tim/prune/data/RangeStats.java
src/tim/prune/data/RangeStatsWithGradients.java [new file with mode: 0644]
src/tim/prune/data/Selection.java
src/tim/prune/data/Timestamp.java
src/tim/prune/function/AboutScreen.java
src/tim/prune/function/AddAltitudeOffset.java
src/tim/prune/function/AddTimeOffset.java
src/tim/prune/function/ConnectToPointFunction.java
src/tim/prune/function/CreateMarkerWaypointsFunction.java
src/tim/prune/function/DistanceTimeLimitFunction.java
src/tim/prune/function/DuplicatePoint.java
src/tim/prune/function/FullRangeDetails.java [deleted file]
src/tim/prune/function/InterpolateFunction.java
src/tim/prune/function/PasteCoordinateList.java [new file with mode: 0644]
src/tim/prune/function/PasteCoordinates.java
src/tim/prune/function/PlayAudioFunction.java
src/tim/prune/function/PlusCodeFunction.java [new file with mode: 0644]
src/tim/prune/function/ProjectPoint.java [new file with mode: 0644]
src/tim/prune/function/RearrangeWaypointsFunction.java
src/tim/prune/function/SearchOpenCachingDeFunction.java
src/tim/prune/function/ShowFullDetails.java [new file with mode: 0644]
src/tim/prune/function/compress/CompressTrackFunction.java
src/tim/prune/function/compress/SingleParameterAlgorithm.java
src/tim/prune/function/distance/DistanceFunction.java
src/tim/prune/function/distance/DistanceTableModel.java
src/tim/prune/function/estimate/EstimateTime.java
src/tim/prune/function/estimate/EstimationParameters.java
src/tim/prune/function/estimate/LearnParameters.java
src/tim/prune/function/gpsies/FormPoster.java [deleted file]
src/tim/prune/function/gpsies/GetGpsiesFunction.java [deleted file]
src/tim/prune/function/gpsies/GpsiesXmlHandler.java [deleted file]
src/tim/prune/function/gpsies/UploadGpsiesFunction.java [deleted file]
src/tim/prune/function/olc/CoordPair.java [new file with mode: 0644]
src/tim/prune/function/olc/OlcArea.java [new file with mode: 0644]
src/tim/prune/function/olc/OlcDecoder.java [new file with mode: 0644]
src/tim/prune/function/search/GenericDownloaderFunction.java
src/tim/prune/function/search/SearchResult.java
src/tim/prune/function/search/TrackListModel.java [moved from src/tim/prune/function/gpsies/TrackListModel.java with 95% similarity]
src/tim/prune/function/search/wikimedia_galleries.txt
src/tim/prune/function/settings/SaveConfig.java
src/tim/prune/function/settings/SetDisplaySettings.java
src/tim/prune/function/settings/SetLanguage.java
src/tim/prune/function/settings/SetPathsFunction.java
src/tim/prune/function/sew/SplitSegmentsFunction.java
src/tim/prune/function/srtm/LookupSrtmFunction.java
src/tim/prune/function/srtm/TileFinder.java
src/tim/prune/gui/CoordDisplay.java [new file with mode: 0644]
src/tim/prune/gui/DetailsDisplay.java
src/tim/prune/gui/MenuManager.java
src/tim/prune/gui/StatusBar.java
src/tim/prune/gui/Viewport.java
src/tim/prune/gui/colour/ColourerFactory.java
src/tim/prune/gui/colour/FileColourer.java
src/tim/prune/gui/map/DiskTileCacher.java
src/tim/prune/gui/map/MapCanvas.java
src/tim/prune/gui/map/MapSourceLibrary.java
src/tim/prune/gui/map/MapTileManager.java
src/tim/prune/gui/map/TileDownloader.java
src/tim/prune/gui/profile/ProfileChart.java
src/tim/prune/jpeg/drew/ExifTiffHandler.java
src/tim/prune/lang/prune-texts_af.properties
src/tim/prune/lang/prune-texts_cy.properties [new file with mode: 0644]
src/tim/prune/lang/prune-texts_cz.properties
src/tim/prune/lang/prune-texts_da.properties
src/tim/prune/lang/prune-texts_de.properties
src/tim/prune/lang/prune-texts_de_CH.properties
src/tim/prune/lang/prune-texts_en.properties
src/tim/prune/lang/prune-texts_en_US.properties
src/tim/prune/lang/prune-texts_es.properties
src/tim/prune/lang/prune-texts_fi.properties
src/tim/prune/lang/prune-texts_fr.properties
src/tim/prune/lang/prune-texts_hu.properties
src/tim/prune/lang/prune-texts_it.properties
src/tim/prune/lang/prune-texts_ja.properties
src/tim/prune/lang/prune-texts_ko.properties
src/tim/prune/lang/prune-texts_nl.properties
src/tim/prune/lang/prune-texts_no.properties
src/tim/prune/lang/prune-texts_pl.properties
src/tim/prune/lang/prune-texts_pt.properties
src/tim/prune/lang/prune-texts_ro.properties
src/tim/prune/lang/prune-texts_ru.properties
src/tim/prune/lang/prune-texts_sv.properties
src/tim/prune/lang/prune-texts_tr.properties
src/tim/prune/lang/prune-texts_uk.properties
src/tim/prune/lang/prune-texts_zh.properties
src/tim/prune/load/ContentCacher.java [new file with mode: 0644]
src/tim/prune/load/FileCacher.java
src/tim/prune/load/FileSplitter.java
src/tim/prune/load/JpegLoader.java
src/tim/prune/load/TextCacher.java [new file with mode: 0644]
src/tim/prune/load/TextFileLoader.java
src/tim/prune/readme.txt
src/tim/prune/save/BaseImageConfigDialog.java
src/tim/prune/save/ImageExporter.java

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8e9e794
--- /dev/null
@@ -0,0 +1,2 @@
+*.class
+*.jar
index 9e9c08b39e92118fefa1561f332b4add6f6b7d54..1a1aa06ddf261d3ce0d44b8610156ce1baca0b5a 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 19.2, 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.
+Here on github you'll find all the sources from version 1 to the current version 20, 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 and some missing French texts are online, to see whether it's a workable idea or not.  Please help with these if you can.
index b38e2ce60f042c9a31274ee4a6dfae683219843f..193244bb3319fa81e974dd1d9f09fbbb52c7f70c 100644 (file)
@@ -1,6 +1,7 @@
 # Build script
+set -e
 # Version number
-PRUNENAME=gpsprune_19.2
+PRUNENAME=gpsprune_20
 # remove compile directory
 rm -rf compile
 # remove dist directory
index 82b29ac8a8969475038b95377cacefb182b26d52..15b6562639083b2eba02a431e820e46a75c8e0e9 100644 (file)
@@ -7,7 +7,7 @@
 
        <groupId>tim.prune</groupId>
        <artifactId>gpsprune</artifactId>
-       <version>19.2</version>
+       <version>20</version>
        <packaging>jar</packaging>
 
        <name>tim.prune.gpsprune</name>
index af259261217d8ce2749853a802e4de6ec4f2547e..fe5699b021d0d7d613f4832110fdc957de74e9a9 100644 (file)
@@ -1 +1 @@
-version=19.2
+version=20
index 3a778588ab815a7f30c4b396e4de9d2c266836da..94e10e0e2ed873e6bbf7558419de14e393c20f08 100644 (file)
@@ -69,7 +69,7 @@ public class App
        private AppMode _appMode = AppMode.NORMAL;
 
        /** Enum for the app mode - currently only two options but may expand later */
-       public enum AppMode {NORMAL, DRAWRECT};
+       public enum AppMode {NORMAL, DRAWRECT}
 
 
        /**
@@ -649,7 +649,8 @@ public class App
                loadedTrack.load(inFieldArray, inDataArray, inOptions);
                if (loadedTrack.getNumPoints() <= 0)
                {
-                       showErrorMessage("error.load.dialogtitle", "error.load.nopoints");
+                       String msgKey = (inSourceInfo == null ? "error.load.nopointsintext" : "error.load.nopoints");
+                       showErrorMessage("error.load.dialogtitle", msgKey);
                        // load next file if there's a queue
                        loadNextFile();
                        return;
@@ -715,9 +716,12 @@ public class App
                                undo.setNumPhotosAudios(_trackInfo.getPhotoList().getNumPhotos(), _trackInfo.getAudioList().getNumAudios());
                                _undoStack.add(undo);
                                _track.combine(inLoadedTrack);
-                               // set source information
-                               inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
-                               _trackInfo.getFileInfo().addSource(inSourceInfo);
+                               if (inSourceInfo != null)
+                               {
+                                       // set source information
+                                       inSourceInfo.populatePointObjects(_track, inLoadedTrack.getNumPoints());
+                                       _trackInfo.getFileInfo().addSource(inSourceInfo);
+                               }
                        }
                        else if (answer == JOptionPane.NO_OPTION)
                        {
@@ -732,8 +736,12 @@ public class App
                                _lastSavePosition = _undoStack.size();
                                _trackInfo.getSelection().clearAll();
                                _track.load(inLoadedTrack);
-                               inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
-                               _trackInfo.getFileInfo().replaceSource(inSourceInfo);
+                               if (inSourceInfo != null)
+                               {
+                                       // set source information
+                                       inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
+                                       _trackInfo.getFileInfo().replaceSource(inSourceInfo);
+                               }
                                _trackInfo.getPhotoList().removeCorrelatedPhotos();
                                _trackInfo.getAudioList().removeCorrelatedAudios();
                        }
@@ -747,16 +755,22 @@ public class App
                        _lastSavePosition = _undoStack.size();
                        _trackInfo.getSelection().clearAll();
                        _track.load(inLoadedTrack);
-                       inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
-                       _trackInfo.getFileInfo().addSource(inSourceInfo);
+                       if (inSourceInfo != null)
+                       {
+                               inSourceInfo.populatePointObjects(_track, _track.getNumPoints());
+                               _trackInfo.getFileInfo().addSource(inSourceInfo);
+                       }
                }
                // Update config before subscribers are told
-               boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
-               Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
+               if (inSourceInfo != null)
+               {
+                       boolean isRegularLoad = (inSourceInfo.getFileType() != FILE_TYPE.GPSBABEL);
+                       Config.getRecentFileList().addFile(new RecentFile(inSourceInfo.getFile(), isRegularLoad));
+                       // Update status bar
+                       UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
+                               + " '" + inSourceInfo.getName() + "'");
+               }
                UpdateMessageBroker.informSubscribers();
-               // Update status bar
-               UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.loadfile")
-                       + " '" + inSourceInfo.getName() + "'");
                // update menu
                _menuManager.informFileLoaded();
                // Remove busy lock
index 0441826164d930de8b79acb7d45fd84a742d33ce..68303ed95946466daf23cf1adc5b15aa24f31786 100644 (file)
@@ -18,12 +18,12 @@ 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.ProjectPoint;
+import tim.prune.function.ShowFullDetails;
 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;
@@ -50,8 +50,6 @@ import tim.prune.function.distance.DistanceFunction;
 import tim.prune.function.edit.PointNameEditor;
 import tim.prune.function.estimate.EstimateTime;
 import tim.prune.function.estimate.LearnParameters;
-import tim.prune.function.gpsies.GetGpsiesFunction;
-import tim.prune.function.gpsies.UploadGpsiesFunction;
 import tim.prune.function.settings.SaveConfig;
 import tim.prune.function.settings.SetAltitudeTolerance;
 import tim.prune.function.settings.SetColours;
@@ -88,6 +86,7 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_IMPORTBABEL = null;
        public static GenericFunction FUNCTION_SAVECONFIG  = null;
        public static GenericFunction FUNCTION_EDIT_WAYPOINT_NAME = null;
+       public static GenericFunction FUNCTION_PROJECT_POINT = null;
        public static GenericFunction FUNCTION_REARRANGE_WAYPOINTS = null;
        public static GenericFunction FUNCTION_SELECT_SEGMENT = null;
        public static GenericFunction FUNCTION_SPLIT_SEGMENTS = null;
@@ -112,7 +111,6 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_ADD_ALTITUDE_OFFSET  = null;
        public static GenericFunction FUNCTION_CONVERT_NAMES_TO_TIMES  = null;
        public static GenericFunction FUNCTION_DELETE_FIELD_VALUES  = null;
-       public static GenericFunction FUNCTION_PASTE_COORDINATES = null;
        public static GenericFunction FUNCTION_FIND_WAYPOINT = null;
        public static GenericFunction FUNCTION_DUPLICATE_POINT = null;
        public static GenericFunction FUNCTION_CONNECT_TO_POINT = null;
@@ -127,12 +125,10 @@ public abstract class FunctionLibrary
        public static GenericFunction FUNCTION_CHARTS = null;
        public static GenericFunction FUNCTION_3D     = null;
        public static GenericFunction FUNCTION_DISTANCES  = null;
-       public static GenericFunction FUNCTION_FULL_RANGE_DETAILS = null;
+       public static GenericFunction FUNCTION_FULL_DETAILS = null;
        public static GenericFunction FUNCTION_AUTOPLAY_TRACK = null;
        public static GenericFunction FUNCTION_ESTIMATE_TIME = null;
        public static GenericFunction FUNCTION_LEARN_ESTIMATION_PARAMS = null;
-       public static GenericFunction FUNCTION_GET_GPSIES = null;
-       public static GenericFunction FUNCTION_UPLOAD_GPSIES = null;
        public static GenericFunction FUNCTION_GET_WEATHER_FORECAST = null;
        public static GenericFunction FUNCTION_LOAD_AUDIO = null;
        public static GenericFunction FUNCTION_REMOVE_AUDIO = null;
@@ -168,6 +164,7 @@ public abstract class FunctionLibrary
                FUNCTION_IMPORTBABEL = new BabelLoadFromFile(inApp);
                FUNCTION_SAVECONFIG = new SaveConfig(inApp);
                FUNCTION_EDIT_WAYPOINT_NAME = new PointNameEditor(inApp);
+               FUNCTION_PROJECT_POINT = new ProjectPoint(inApp);
                FUNCTION_REARRANGE_WAYPOINTS = new RearrangeWaypointsFunction(inApp);
                FUNCTION_SELECT_SEGMENT = new SelectSegmentFunction(inApp);
                FUNCTION_SPLIT_SEGMENTS = new SplitSegmentsFunction(inApp);
@@ -192,7 +189,6 @@ public abstract class FunctionLibrary
                FUNCTION_ADD_ALTITUDE_OFFSET = new AddAltitudeOffset(inApp);
                FUNCTION_CONVERT_NAMES_TO_TIMES = new ConvertNamesToTimes(inApp);
                FUNCTION_DELETE_FIELD_VALUES = new DeleteFieldValues(inApp);
-               FUNCTION_PASTE_COORDINATES = new PasteCoordinates(inApp);
                FUNCTION_FIND_WAYPOINT = new FindWaypoint(inApp);
                FUNCTION_DUPLICATE_POINT = new DuplicatePoint(inApp);
                FUNCTION_CONNECT_TO_POINT = new ConnectToPointFunction(inApp);
@@ -206,12 +202,10 @@ public abstract class FunctionLibrary
                FUNCTION_CHARTS = new Charter(inApp);
                FUNCTION_3D     = new ShowThreeDFunction(inApp);
                FUNCTION_DISTANCES = new DistanceFunction(inApp);
-               FUNCTION_FULL_RANGE_DETAILS = new FullRangeDetails(inApp);
+               FUNCTION_FULL_DETAILS = new ShowFullDetails(inApp);
                FUNCTION_AUTOPLAY_TRACK = new AutoplayFunction(inApp);
                FUNCTION_ESTIMATE_TIME = new EstimateTime(inApp);
                FUNCTION_LEARN_ESTIMATION_PARAMS = new LearnParameters(inApp);
-               FUNCTION_GET_GPSIES = new GetGpsiesFunction(inApp);
-               FUNCTION_UPLOAD_GPSIES = new UploadGpsiesFunction(inApp);
                FUNCTION_GET_WEATHER_FORECAST = new GetWeatherForecastFunction(inApp);
                FUNCTION_LOAD_AUDIO = new AudioLoader(inApp);
                FUNCTION_REMOVE_AUDIO = new RemoveAudioFunction(inApp);
index f396e9e4fdbc31a4c1ca2deb67cbe24eff7c6b76..874ece5a7b7dce6314c5179525b516cf88e31e56 100644 (file)
@@ -9,9 +9,11 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Locale;
+
 import javax.swing.JFrame;
 import javax.swing.JSplitPane;
 import javax.swing.JToolBar;
+import javax.swing.UIManager;
 import javax.swing.WindowConstants;
 
 import tim.prune.config.Config;
@@ -29,16 +31,16 @@ import tim.prune.gui.profile.ProfileChart;
 /**
  * GpsPrune is a tool to visualize, edit, convert and prune GPS data
  * Please see the included readme.txt or https://activityworkshop.net
- * This software is copyright activityworkshop.net 2006-2018 and made available through the Gnu GPL version 2.
+ * This software is copyright activityworkshop.net 2006-2020 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 = "19.2";
+       public static final String VERSION_NUMBER = "20";
        /** Build number, just used for about screen */
-       public static final String BUILD_NUMBER = "363d";
+       public static final String BUILD_NUMBER = "378";
        /** Static reference to App object */
        private static App APP = null;
 
@@ -149,6 +151,14 @@ public class GpsPrune
                                Config.setConfigString(Config.KEY_LANGUAGE_FILE, "");
                        }
                }
+
+               // Set look-and-feel
+               try {
+                       String windowStyle = Config.getConfigString(Config.KEY_WINDOW_STYLE);
+                       UIManager.setLookAndFeel(windowStyle);
+               }
+               catch (Exception e) {}
+
                // Set up the window and go
                launch(dataFiles);
        }
index fbf781eb3fef3a79adbdc72548eb5b3c66c83f91..a271f81b9fc5a579ddcce33397a8cb3fbc110ab7 100644 (file)
@@ -18,10 +18,10 @@ import java.util.ResourceBundle;
 public abstract class I18nManager
 {
        /** Properties object into which all the texts are copied */
-       private static Properties LocalTexts = null;
+       private static Properties _localTexts = null;
 
        /** External properties file for developer testing */
-       private static Properties ExternalPropsFile = null;
+       private static Properties _externalPropsFile = null;
 
 
        /**
@@ -33,7 +33,7 @@ public abstract class I18nManager
                final String BUNDLE_NAME = "tim.prune.lang.prune-texts";
                final Locale BACKUP_LOCALE = new Locale("en", "GB");
 
-               LocalTexts = new Properties();
+               _localTexts = new Properties();
                // Load English texts first to use as defaults
                loadFromBundle(ResourceBundle.getBundle(BUNDLE_NAME, BACKUP_LOCALE));
 
@@ -65,7 +65,7 @@ public abstract class I18nManager
                while (e.hasMoreElements())
                {
                        String key = e.nextElement();
-                       LocalTexts.setProperty(key, inBundle.getString(key));
+                       _localTexts.setProperty(key, inBundle.getString(key));
                }
        }
 
@@ -81,9 +81,9 @@ public abstract class I18nManager
                try
                {
                        File file = new File(inFilename);
-                       ExternalPropsFile = new Properties();
+                       _externalPropsFile = new Properties();
                        fis = new FileInputStream(file);
-                       ExternalPropsFile.load(fis);
+                       _externalPropsFile.load(fis);
                        fileLoaded = true; // everything worked
                }
                catch (IOException ioe) {}
@@ -103,17 +103,17 @@ public abstract class I18nManager
        public static String getText(String inKey)
        {
                // look in external props file if available
-               if (ExternalPropsFile != null)
+               if (_externalPropsFile != null)
                {
-                       String extText = ExternalPropsFile.getProperty(inKey);
+                       String extText = _externalPropsFile.getProperty(inKey);
                        if (extText != null) return extText;
                }
                // look in texts if available
-               if (LocalTexts != null)
+               if (_localTexts != null)
                {
                        try
                        {
-                               String localText = LocalTexts.getProperty(inKey);
+                               String localText = _localTexts.getProperty(inKey);
                                if (localText != null) return localText;
                        }
                        catch (MissingResourceException mre) {}
index 84e5fd7dc6b4a248bb1d2bef51f18be225f90425..8c6eefec498706d0c789ad1ae3b4302a8e4022ac 100644 (file)
@@ -55,6 +55,8 @@ public abstract class Config
        public static final String KEY_POVRAY_FONT = "prune.povrayfont";
        /** Key for the selected unit set */
        public static final String KEY_UNITSET_KEY  = "prune.unitsetkey";
+       /** Key for the selected coordinate display format */
+       public static final String KEY_COORD_DISPLAY_FORMAT  = "prune.coorddisplay";
        /** Key for index of map source */
        public static final String KEY_MAPSOURCE_INDEX = "prune.mapsource";
        /** Key for number of fixed map sources */
@@ -87,6 +89,8 @@ public abstract class Config
        public static final String KEY_ANTIALIAS = "prune.antialias";
        /** Key for kml track colour */
        public static final String KEY_KML_TRACK_COLOUR = "prune.kmltrackcolour";
+       /** Key for window style (name of look-and-feel) */
+       public static final String KEY_WINDOW_STYLE = "prune.windowstyle";
        /** Key for autosaving settings */
        public static final String KEY_AUTOSAVE_SETTINGS = "prune.autosavesettings";
        /** Key for recently used files */
@@ -170,6 +174,8 @@ public abstract class Config
                _unitSet = UnitSetLibrary.getUnitSet(_configValues.getProperty(KEY_UNITSET_KEY));
                // Adjust map source index if necessary
                adjustSelectedMap();
+               // Reset coord display format
+               setConfigInt(KEY_COORD_DISPLAY_FORMAT, 0);
 
                if (loadFailed) {
                        throw new ConfigException();
@@ -197,6 +203,7 @@ public abstract class Config
                props.put(KEY_ANTIALIAS, "1"); // antialias on by default
                props.put(KEY_AUTOSAVE_SETTINGS, "0"); // autosave false by default
                props.put(KEY_UNITSET_KEY, "unitset.kilometres"); // metric by default
+               props.put(KEY_COORD_DISPLAY_FORMAT, "0"); // original
                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
@@ -246,6 +253,14 @@ public abstract class Config
                return _configFile;
        }
 
+       /**
+        * Set the file to which config was saved
+        */
+       public static void setConfigFile(File inFile)
+       {
+               _configFile = inFile;
+       }
+
        /**
         * @return config Properties object to allow all config values to be saved
         */
@@ -379,7 +394,7 @@ public abstract class Config
        public static void updatePointColourer(PointColourer inColourer)
        {
                _pointColourer = inColourer;
-               setConfigString(KEY_POINT_COLOURER, ColourerFactory.PointColourerToString(_pointColourer));
+               setConfigString(KEY_POINT_COLOURER, ColourerFactory.pointColourerToString(_pointColourer));
        }
 
        /**
index 3022774654f3f6b66501b143e2ca113bdc103eaa..db7f2dd61157be5b834af0d2cb491708e8d806f4 100644 (file)
@@ -1,4 +1,4 @@
-The source code of GpsPrune is copyright 2006-2018 activityworkshop.net
+The source code of GpsPrune is copyright 2006-2020 activityworkshop.net
 and is distributed under the terms of the Gnu GPL version 2.
 
 Portions of the package jpeg.drew were taken
@@ -6,4 +6,4 @@ 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
+in the source code and some preferred to remain anonymous.
index e30fa4ac357d89a68041fa07eee2f76bf5e8ee22..b668660236a5d164e710a4963a7b7c0ff64975fa 100644 (file)
@@ -475,4 +475,22 @@ public abstract class Coordinate
                return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "."
                        + formatFraction(_fracs, _fracDenom) + ") = " + _asDouble;
        }
+
+       /**
+        * From a saved coordinate format display value, get the corresponding value to use
+        * @param inValue value from config
+        * @return coordinate format as int
+        */
+       public static int getCoordinateFormatForDisplay(int inValue)
+       {
+               switch(inValue)
+               {
+                       case FORMAT_DEG:
+                       case FORMAT_DEG_MIN:
+                       case FORMAT_DEG_MIN_SEC:
+                               return inValue;
+                       default:
+                               return FORMAT_NONE;
+               }
+       }
 }
index 41900cb8736df60d668238306079256bf2dca175..0c4ee5fd4606624b8de9ce00af8f57a3284ccee1 100644 (file)
@@ -51,7 +51,9 @@ public class FileInfo
         */
        public void removeSource()
        {
-               _sources.remove(_sources.size()-1);
+               if (!_sources.isEmpty()) {
+                       _sources.remove(_sources.size()-1);
+               }
        }
 
        /**
index 13ecaade10d4e6a8d0eccc87a383762ae399cc00..d8461c476b122a58561d215296b1fe7370aef74b 100644 (file)
 package tim.prune.data;
 
-import tim.prune.config.Config;
-
 /**
- * Class to do calculations of range statistics such as distances, durations,
- * speeds, gradients etc, and to hold the results of the calculations.
- * Used by FullRangeDetails as well as the EstimateTime functions.
+ * Class to do basic calculations of range statistics such as distances, durations,
+ * and altitude ranges, and to hold the results of the calculations.
  */
 public class RangeStats
 {
-       // MAYBE: Split into basic stats (quick to calculate, for detailsdisplay) and full stats (for other two)
-       private boolean _valid = false;
-       private int     _numPoints   = 0;
-       private int     _startIndex = 0, _endIndex = 0;
-       private int     _numSegments = 0;
-       private AltitudeRange _totalAltitudeRange = null, _movingAltitudeRange = null;
-       private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null;
+       private int _numPoints   = 0;
+       private int _numSegments = 0;
+       private boolean _foundTrackPoint = false;
+       protected AltitudeRange _totalAltitudeRange = new AltitudeRange();
+       protected AltitudeRange _movingAltitudeRange = new AltitudeRange();
        private Timestamp _earliestTimestamp = null, _latestTimestamp = null;
        private long _movingMilliseconds = 0L;
        private boolean _timesIncomplete = false;
        private boolean _timesOutOfSequence = false;
-       private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
-       // Note, maximum speed is not calculated here, use the SpeedData class instead
+       protected double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
+       protected DataPoint _prevPoint = null;
 
-       private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
 
+       /** Constructor */
+       public RangeStats()
+       {}
 
        /**
-        * Constructor
-        * @param inTrack track to compile data for
-        * @param inStartIndex start index of range to examine
-        * @param inEndIndex end index (inclusive) of range to examine
+        * Constructor giving Track
+        * @param inTrack track object to calculate with
         */
        public RangeStats(Track inTrack, int inStartIndex, int inEndIndex)
        {
-               if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex
-                       && inEndIndex < inTrack.getNumPoints())
+               populateFromTrack(inTrack, inStartIndex, inEndIndex);
+       }
+
+       /**
+        * Add the specified points from the given track to the calculations
+        * @param inTrack track object
+        * @param inStartIndex start index (inclusive)
+        * @param inEndIndex end index (inclusive)
+        */
+       protected void populateFromTrack(Track inTrack, int inStartIndex, int inEndIndex)
+       {
+               for (int i=inStartIndex; i<=inEndIndex; i++)
                {
-                       _valid = calculateStats(inTrack, inStartIndex, inEndIndex);
+                       addPoint(inTrack.getPoint(i));
                }
        }
 
        /**
-        * Calculate the statistics and populate the member variables with the results
-        * @param inTrack track
-        * @param inStartIndex start index of range
-        * @param inEndIndex end index (inclusive) of range
-        * @return true on success
+        * @param inPoint point to add to the calculations
         */
-       private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex)
+       public void addPoint(DataPoint inPoint)
        {
-               _startIndex = inStartIndex;
-               _endIndex = inEndIndex;
-               _numPoints = inEndIndex - inStartIndex + 1;
-               _totalAltitudeRange  = new AltitudeRange();
-               _movingAltitudeRange = new AltitudeRange();
-               _gentleAltitudeRange = new AltitudeRange();
-               _steepAltitudeRange  = new AltitudeRange();
-               DataPoint prevPoint = null;
-               Altitude prevAltitude = null;
-               _totalDistanceRads = _movingDistanceRads = 0.0;
-               double radsSinceLastAltitude = 0.0;
-               _movingMilliseconds = 0L;
-
-               // Loop over the points in the range
-               for (int i=inStartIndex; i<= inEndIndex; i++)
+               if (inPoint == null)
+               {
+                       return;
+               }
+               _numPoints++;
+               // ignore all waypoints
+               if (inPoint.isWaypoint()) {
+                       return;
+               }
+               if (inPoint.getSegmentStart() || !_foundTrackPoint) {
+                       _numSegments++;
+               }
+               _foundTrackPoint = true;
+               // Get the distance to the previous track point
+               if (_prevPoint != null)
                {
-                       DataPoint p = inTrack.getPoint(i);
-                       if (p == null) return false;
-                       // ignore all waypoints
-                       if (p.isWaypoint()) continue;
+                       double rads = DataPoint.calculateRadiansBetween(_prevPoint, inPoint);
+                       _totalDistanceRads += rads;
+                       if (!inPoint.getSegmentStart()) {
+                               _movingDistanceRads += rads;
+                       }
+               }
 
-                       if (p.getSegmentStart()) {
-                               _numSegments++;
+               // timestamps
+               if (inPoint.hasTimestamp())
+               {
+                       Timestamp currTstamp = inPoint.getTimestamp();
+                       if (_earliestTimestamp == null || currTstamp.isBefore(_earliestTimestamp)) {
+                               _earliestTimestamp = currTstamp;
                        }
-                       // Get the distance to the previous track point
-                       if (prevPoint != null)
-                       {
-                               double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
-                               _totalDistanceRads += rads;
-                               if (!p.getSegmentStart()) {
-                                       _movingDistanceRads += rads;
-                               }
-                               // Keep track of rads since last point with an altitude
-                               radsSinceLastAltitude += rads;
+                       if (_latestTimestamp == null || currTstamp.isAfter(_latestTimestamp)) {
+                               _latestTimestamp = currTstamp;
                        }
-                       // Get the altitude difference to the previous track point
-                       if (p.hasAltitude())
+                       // Work out duration without segment gaps
+                       if (!inPoint.getSegmentStart() && _prevPoint != null && _prevPoint.hasTimestamp())
                        {
-                               Altitude altitude = p.getAltitude();
-                               _totalAltitudeRange.addValue(altitude);
-                               if (p.getSegmentStart()) {
-                                       _movingAltitudeRange.ignoreValue(altitude);
+                               long millisLater = currTstamp.getMillisecondsSince(_prevPoint.getTimestamp());
+                               if (millisLater < 0) {
+                                       _timesOutOfSequence = true;
                                }
-                               else
-                               {
-                                       _movingAltitudeRange.addValue(altitude);
-                                       if (prevAltitude != null)
-                                       {
-                                               // Work out gradient, see whether to ignore/add to gentle or steep
-                                               double heightDiff = altitude.getMetricValue() - prevAltitude.getMetricValue();
-                                               double metricDist = Distance.convertRadiansToDistance(radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
-                                               final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
-                                               if (isSteep) {
-                                                       _steepAltitudeRange.ignoreValue(prevAltitude);
-                                                       _steepAltitudeRange.addValue(altitude);
-                                               }
-                                               else {
-                                                       _gentleAltitudeRange.ignoreValue(prevAltitude);
-                                                       _gentleAltitudeRange.addValue(altitude);
-                                               }
-                                       }
+                               else {
+                                       _movingMilliseconds += millisLater;
                                }
-                               prevAltitude = altitude;
-                               radsSinceLastAltitude = 0.0;
                        }
+               }
+               else {
+                       _timesIncomplete = true;
+               }
 
-                       if (p.hasTimestamp())
-                       {
-                               if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
-                                       _earliestTimestamp = p.getTimestamp();
-                               }
-                               if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
-                                       _latestTimestamp = p.getTimestamp();
-                               }
-                               // Work out duration without segment gaps
-                               if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
-                               {
-                                       long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
-                                       if (millisLater < 0) {_timesOutOfSequence = true;}
-                                       else {
-                                               _movingMilliseconds += millisLater;
-                                       }
-                               }
+               // altitudes
+               if (inPoint.hasAltitude())
+               {
+                       Altitude altitude = inPoint.getAltitude();
+                       _totalAltitudeRange.addValue(altitude);
+                       if (inPoint.getSegmentStart()) {
+                               _movingAltitudeRange.ignoreValue(altitude);
                        }
-                       else {
-                               _timesIncomplete = true;
+                       else
+                       {
+                               _movingAltitudeRange.addValue(altitude);
                        }
-
-                       prevPoint = p;
                }
-               return true;
-       }
 
+               // allow child classes to do additional calculations
+               doFurtherCalculations(inPoint);
 
-       /** @return true if results are valid */
-       public boolean isValid() {
-               return _valid;
+               _prevPoint = inPoint;
        }
 
-       /** @return start index of range */
-       public int getStartIndex() {
-               return _startIndex;
+       /**
+        * Hook for subclasses to do what they want in addition
+        * @param inPoint incoming point
+        */
+       protected void doFurtherCalculations(DataPoint inPoint)
+       {
        }
 
-       /** @return end index of range */
-       public int getEndIndex() {
-               return _endIndex;
-       }
 
        /** @return number of points in range */
        public int getNumPoints() {
@@ -179,16 +149,6 @@ public class RangeStats
                return _movingAltitudeRange;
        }
 
-       /** @return altitude range of range just considering low gradient bits */
-       public AltitudeRange getGentleAltitudeRange() {
-               return _gentleAltitudeRange;
-       }
-
-       /** @return altitude range of range just considering high gradient bits */
-       public AltitudeRange getSteepAltitudeRange() {
-               return _steepAltitudeRange;
-       }
-
        /** @return the earliest timestamp found */
        public Timestamp getEarliestTimestamp() {
                return _earliestTimestamp;
@@ -239,42 +199,26 @@ public class RangeStats
                return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
        }
 
-       /** @return the total gradient in % (including segment gaps) */
-       public double getTotalGradient()
-       {
-               double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
-               if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
-                       return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
-               }
-               return 0.0;
-       }
-
-       /** @return the moving gradient in % (ignoring segment gaps) */
-       public double getMovingGradient()
-       {
-               double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
-               if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
-                       return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
-               }
-               return 0.0;
-       }
-
-       /** @return the total vertical speed (including segment gaps) in current vspeed units */
+       /**
+        * @return the total vertical speed (including segment gaps) in metric units
+        */
        public double getTotalVerticalSpeed()
        {
                long time = getTotalDurationInSeconds();
                if (time > 0 && _totalAltitudeRange.hasRange()) {
-                       return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
+                       return _totalAltitudeRange.getMetricHeightDiff() / time;
                }
                return 0.0;
        }
 
-       /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
+       /**
+        * @return the moving vertical speed (ignoring segment gaps) in metric units
+        */
        public double getMovingVerticalSpeed()
        {
                long time = getMovingDurationInSeconds();
                if (time > 0 && _movingAltitudeRange.hasRange()) {
-                       return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
+                       return _movingAltitudeRange.getMetricHeightDiff() / time;
                }
                return 0.0;
        }
diff --git a/src/tim/prune/data/RangeStatsWithGradients.java b/src/tim/prune/data/RangeStatsWithGradients.java
new file mode 100644 (file)
index 0000000..c495c51
--- /dev/null
@@ -0,0 +1,106 @@
+package tim.prune.data;
+
+/**
+ * Class to do additional range calculations including gradients
+ * Used by full details display as well as the EstimateTime functions.
+ */
+public class RangeStatsWithGradients extends RangeStats
+{
+       private AltitudeRange _gentleAltitudeRange = new AltitudeRange();
+       private AltitudeRange _steepAltitudeRange = new AltitudeRange();
+       private Altitude _prevAltitude = null;
+       private double _radsSinceLastAltitude = 0.0;
+
+       private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
+
+
+       /**
+        * Default constructor
+        */
+       public RangeStatsWithGradients()
+       {
+               super();
+       }
+
+       /**
+        * Constructor
+        * @param inTrack track object
+        * @param inStartIndex start index
+        * @param inEndIndex end index
+        */
+       public RangeStatsWithGradients(Track inTrack, int inStartIndex, int inEndIndex)
+       {
+               super();
+               populateFromTrack(inTrack, inStartIndex, inEndIndex);
+       }
+
+       /**
+        * Add the given point to the calculations
+        * @param inPoint incoming point
+        */
+       protected void doFurtherCalculations(DataPoint inPoint)
+       {
+               if (_prevPoint != null)
+               {
+                       // Keep track of rads since last point with an altitude
+                       double rads = DataPoint.calculateRadiansBetween(_prevPoint, inPoint);
+                       _radsSinceLastAltitude += rads;
+               }
+
+               if (inPoint.hasAltitude())
+               {
+                       Altitude altitude = inPoint.getAltitude();
+
+                       if (!inPoint.getSegmentStart() && _prevAltitude != null)
+                       {
+                               // Work out gradient, see whether to ignore/add to gentle or steep
+                               double heightDiff = altitude.getMetricValue() - _prevAltitude.getMetricValue();
+                               double metricDist = Distance.convertRadiansToDistance(_radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
+                               final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
+                               if (isSteep)
+                               {
+                                       _steepAltitudeRange.ignoreValue(_prevAltitude);
+                                       _steepAltitudeRange.addValue(altitude);
+                               }
+                               else
+                               {
+                                       _gentleAltitudeRange.ignoreValue(_prevAltitude);
+                                       _gentleAltitudeRange.addValue(altitude);
+                               }
+                       }
+                       _prevAltitude = altitude;
+                       _radsSinceLastAltitude = 0.0;
+               }
+
+       }
+
+       /** @return altitude range of range just considering low gradient bits */
+       public AltitudeRange getGentleAltitudeRange() {
+               return _gentleAltitudeRange;
+       }
+
+       /** @return altitude range of range just considering high gradient bits */
+       public AltitudeRange getSteepAltitudeRange() {
+               return _steepAltitudeRange;
+       }
+
+       /** @return the total gradient in % (including segment gaps) */
+       public double getTotalGradient()
+       {
+               double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
+               if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
+                       return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
+               }
+               return 0.0;
+       }
+
+       /** @return the moving gradient in % (ignoring segment gaps) */
+       public double getMovingGradient()
+       {
+               double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
+               if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
+                       return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
+               }
+               return 0.0;
+       }
+}
index 7b63d70838e660c9af142aa1b341f5c2bd40ccce..24fda5df99565270b898602ed75f6e5cca39802e 100644 (file)
@@ -11,14 +11,11 @@ public class Selection
 {
        private Track _track = null;
        private int _currentPoint = -1;
-       private boolean _valid = false;
        private int _prevNumPoints = 0;
        private int _startIndex = -1, _endIndex = -1;
        private int _currentPhotoIndex = -1;
        private int _currentAudioIndex = -1;
-       private AltitudeRange _altitudeRange = null;
-       private long _movingMilliseconds = 0L;
-       private double _angMovingDistance = -1.0;
+       private RangeStats _rangeStats = null;
 
 
        /**
@@ -36,7 +33,7 @@ public class Selection
         */
        public void markInvalid()
        {
-               _valid = false;
+               _rangeStats = null;
        }
 
 
@@ -63,6 +60,9 @@ public class Selection
         */
        private void recalculate()
        {
+               if (_rangeStats != null) {
+                       return;
+               }
                final int numPoints = _track.getNumPoints();
                // Recheck if the number of points has changed
                if (numPoints != _prevNumPoints)
@@ -72,52 +72,12 @@ public class Selection
                }
                if (numPoints > 0 && hasRangeSelected())
                {
-                       _altitudeRange = new AltitudeRange();
-                       Altitude altitude = null;
-                       Timestamp time = null, previousTime = null;
-                       DataPoint lastPoint = null, currPoint = null;
-                       _angMovingDistance = 0.0;
-                       _movingMilliseconds = 0L;
-                       // Loop over points in selection
-                       for (int i=_startIndex; i<=_endIndex; i++)
-                       {
-                               currPoint = _track.getPoint(i);
-                               altitude = currPoint.getAltitude();
-                               // Ignore waypoints in altitude calculations
-                               if (!currPoint.isWaypoint() && altitude.isValid())
-                               {
-                                       if (currPoint.getSegmentStart()) {
-                                               _altitudeRange.ignoreValue(altitude);
-                                       }
-                                       else {
-                                               _altitudeRange.addValue(altitude);
-                                       }
-                               }
-                               // Compare timestamps within the segments
-                               time = currPoint.getTimestamp();
-                               if (time.isValid())
-                               {
-                                       // add moving time
-                                       if (!currPoint.getSegmentStart() && previousTime != null && time.isAfter(previousTime)) {
-                                               _movingMilliseconds += time.getMillisecondsSince(previousTime);
-                                       }
-                                       previousTime = time;
-                               }
-                               // Calculate distances, again ignoring waypoints
-                               if (!currPoint.isWaypoint())
-                               {
-                                       if (lastPoint != null)
-                                       {
-                                               double radians = DataPoint.calculateRadiansBetween(lastPoint, currPoint);
-                                               if (!currPoint.getSegmentStart()) {
-                                                       _angMovingDistance += radians;
-                                               }
-                                       }
-                                       lastPoint = currPoint;
-                               }
-                       }
+                       _rangeStats = new RangeStats(_track, _startIndex, _endIndex);
+               }
+               else
+               {
+                       _rangeStats = new RangeStats();
                }
-               _valid = true;
        }
 
 
@@ -126,7 +86,7 @@ public class Selection
         */
        public int getStart()
        {
-               if (!_valid) recalculate();
+               recalculate();
                return _startIndex;
        }
 
@@ -136,7 +96,7 @@ public class Selection
         */
        public int getEnd()
        {
-               if (!_valid) recalculate();
+               recalculate();
                return _endIndex;
        }
 
@@ -145,8 +105,8 @@ public class Selection
         */
        public AltitudeRange getAltitudeRange()
        {
-               if (!_valid) recalculate();
-               return _altitudeRange;
+               recalculate();
+               return _rangeStats.getTotalAltitudeRange();
        }
 
 
@@ -155,8 +115,8 @@ public class Selection
         */
        public long getMovingSeconds()
        {
-               if (!_valid) recalculate();
-               return _movingMilliseconds / 1000L;
+               recalculate();
+               return _rangeStats.getMovingDurationInSeconds();
        }
 
        /**
@@ -164,7 +124,7 @@ public class Selection
         */
        public double getMovingDistance()
        {
-               return Distance.convertRadiansToDistance(_angMovingDistance);
+               return _rangeStats.getMovingDistance();
        }
 
        /**
@@ -176,6 +136,7 @@ public class Selection
                selectRange(-1, -1);
                _currentPhotoIndex = -1;
                _currentAudioIndex = -1;
+               markInvalid();
                check();
        }
 
@@ -276,6 +237,7 @@ public class Selection
                // Clear selected range
                _startIndex = _endIndex = -1;
                // Check for consistency and fire update
+               markInvalid();
                check();
        }
 
@@ -355,6 +317,7 @@ public class Selection
                {
                        // track is empty, clear selections
                        _currentPoint = _startIndex = _endIndex = -1;
+                       markInvalid();
                }
                UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
        }
index ac144bb8c59e8247deb67438a9f5c8504c258ca9..6083cf676bcc468f06178cd88f04e4f88547fa2e 100644 (file)
@@ -22,7 +22,7 @@ public abstract class Timestamp
        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 boolean _millisAddedToTimeFormat = false;
 
 
        /** Possible formats for parsing and displaying timestamps */
@@ -138,7 +138,7 @@ public abstract class Timestamp
        {
                if (!isValid()) return "";
                // Maybe we should add milliseconds to this format?
-               if (hasMilliseconds() && !MillisAddedToTimeFormat)
+               if (hasMilliseconds() && !_millisAddedToTimeFormat)
                {
                        try
                        {
@@ -147,7 +147,7 @@ public abstract class Timestamp
                                if (pattern.indexOf("ss") > 0 && pattern.indexOf("SS") < 0)
                                {
                                        sdf.applyPattern(pattern.replaceFirst("s+", "$0.SSS"));
-                                       MillisAddedToTimeFormat = true;
+                                       _millisAddedToTimeFormat = true;
                                }
                        }
                        catch (ClassCastException cce) {}
index 80a7983418149d97f3cf7fce1e9915cda968dae2..518b10e1568fbb29a2407f61cfee3682cf568b8f 100644 (file)
@@ -98,7 +98,7 @@ public class AboutScreen extends GenericFunction
                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, 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>");
+                               " \u4e2d\u6587 (chinese), \u65E5\u672C\u8A9E (japanese), \uD55C\uAD6D\uC5B4/\uC870\uC120\uB9D0 (korean), schwiizerd\u00FC\u00FCtsch</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));
index c98ab3e17843a27db638d74ffac802c5400bea36..26849de03711831a49e427b9e7fa7ae34c6d17a9 100644 (file)
@@ -101,7 +101,7 @@ public class AddAltitudeOffset extends GenericFunction
                MouseAdapter mouseListener = new MouseAdapter() {
                        public void mouseReleased(java.awt.event.MouseEvent arg0) {
                                _okButton.setEnabled(Math.abs(getOffset()) > 0.001);
-                       };
+                       }
                };
                _editField.addKeyListener(keyListener);
                _editField.addMouseListener(mouseListener);
index 2e259bb5f5895e94690355d2cbee039080a8434d..115bfa5997102edc0db005074c05fa5a97bb4754 100644 (file)
@@ -126,7 +126,7 @@ public class AddTimeOffset extends GenericFunction
                MouseAdapter mouseListener = new MouseAdapter() {
                        public void mouseReleased(java.awt.event.MouseEvent arg0) {
                                _okButton.setEnabled(getOffsetSecs() != 0L);
-                       };
+                       }
                };
                _dayField.addKeyListener(keyListener);
                _hourField.addKeyListener(keyListener);
index 0998020f2f2f871ed9d1d8522af91d13b72ef9b5..9921dc590aa8bfaed816f4e4456c241bc72c3e7b 100644 (file)
@@ -42,9 +42,9 @@ public class ConnectToPointFunction extends GenericFunction
                boolean connectPhoto = (point != null && photo != null && point.getPhoto() == null);
                boolean connectAudio = (point != null && audio != null && point.getAudio() == null);
 
-               if (connectPhoto && connectAudio) {
+               // if (connectPhoto && connectAudio) {
                        // TODO: Let user choose whether to connect photo/audio or both
-               }
+               // }
                // Make undo object
                UndoOperation undo = new UndoConnectMedia(point, connectPhoto?photo.getName():null,
                        connectAudio?audio.getName():null);
index f3dd1f1d9bca2eba8f78f42a3a136294363543e9..92b4ce56c497b6dbd33924d43f2984cea20ae3a7 100644 (file)
@@ -8,12 +8,14 @@ import tim.prune.UpdateMessageBroker;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Field;
 import tim.prune.data.FieldList;
+import tim.prune.data.RangeStats;
 import tim.prune.data.Track;
+import tim.prune.data.UnitSetLibrary;
 import tim.prune.undo.UndoAppendPoints;
 
 /**
- * Function to create waypoints marking either
- * at regular distance intervals or time intervals
+ * Function to create waypoints marking regular distance intervals,
+ * regular time intervals, or halfway points
  */
 public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
 {
@@ -22,12 +24,21 @@ public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
        /** Counter of previously used multiple */
        private int _previousMultiple = 0;
 
+       /*
+        * Type of halfway point
+        */
+       private enum HalfwayType
+       {
+               HALF_DISTANCE,
+               HALF_CLIMB,
+               HALF_DESCENT
+       }
 
        /**
         * Constructor
         */
        public CreateMarkerWaypointsFunction(App inApp) {
-               super(inApp);
+               super(inApp, true);
        }
 
        /**
@@ -51,55 +62,34 @@ public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
         */
        protected void performFunction()
        {
-               // Distribute either by distance or time
+               // Determine which kind of markers to create
                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);
+               final double distLimitKm = getDistanceLimitKilometres();
+               final boolean createByDistance = (distLimitKm > 0.0);
+               final boolean createHalves = isHalvesSelected();
 
                // 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++)
+               if (createByTime || createByDistance) {
+                       createWaypointsAtIntervals(timeLimitSeconds, distLimitKm);
+               }
+               else if (createHalves)
                {
-                       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;
-                       }
+                       createHalfwayWaypoints();
+               }
+               else
+               {
+                       return;
                }
 
-               // System.out.println(_pointsToAdd.size() + " markers to add...");
                if (!_pointsToAdd.isEmpty())
                {
+                       // Make undo object
+                       final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+                       UndoAppendPoints undo = new UndoAppendPoints(numPoints);
+
                        // Append created points to Track
                        Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE, Field.WAYPT_NAME};
                        final int numPointsToAdd = _pointsToAdd.size();
@@ -109,13 +99,53 @@ public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
                        _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
+                       final String confirmMessage = I18nManager.getTextWithNumber("confirm.pointsadded", _pointsToAdd.size());
+                       _app.completeFunction(undo, confirmMessage);
                        UpdateMessageBroker.informSubscribers();
                }
                _dialog.dispose();
        }
 
+       /**
+        * Create waypoints according to the given intervals
+        * @param inTimeLimitSeconds
+        * @param inDistLimitKm distance limit in kilometres
+        */
+       private void createWaypointsAtIntervals(int inTimeLimitSeconds, double inDistLimitKm)
+       {
+               final boolean createByTime = (inTimeLimitSeconds > 0);
+               final boolean createByDistance = (inDistLimitKm > 0.0);
+
+               // Make new waypoints, looping through the points in the track
+               DataPoint currPoint = null, prevPoint = null;
+               double currValue = 0.0, prevValue = 0.0;
+               RangeStats rangeStats = new RangeStats();
+               final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+               for (int i=0; i<numPoints; i++)
+               {
+                       currPoint = _app.getTrackInfo().getTrack().getPoint(i);
+                       rangeStats.addPoint(currPoint);
+
+                       if (!currPoint.isWaypoint())
+                       {
+                               // Calculate current value
+                               if (createByTime)
+                               {
+                                       currValue = rangeStats.getMovingDurationInSeconds();
+                                       processValue(prevPoint, prevValue, inTimeLimitSeconds, currPoint, currValue);
+                               }
+                               else if (createByDistance)
+                               {
+                                       currValue = rangeStats.getMovingDistanceKilometres();
+                                       processValue(prevPoint, prevValue, inDistLimitKm, currPoint, currValue);
+                               }
+
+                               prevPoint = currPoint;
+                               prevValue = currValue;
+                       }
+               }
+       }
+
        /**
         * Consider a pair of points in the track to see if a new marker should be inserted between them
         * @param inPrevPoint previous point
@@ -138,8 +168,124 @@ public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
                        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;
        }
+
+       /**
+        * Create waypoints for the halfway markers
+        */
+       private void createHalfwayWaypoints()
+       {
+               // Calculate the details of the whole track so we can see what to halve
+               final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+               RangeStats totalStats = new RangeStats();
+               DataPoint currPoint = null;
+               for (int i=0; i<numPoints; i++)
+               {
+                       currPoint = _app.getTrackInfo().getTrack().getPoint(i);
+                       totalStats.addPoint(currPoint);
+               }
+               // Calculate total moving distance of track
+               final double totalDist = totalStats.getMovingDistanceKilometres();
+               // If the track has altitudes, also calculate total climb and total descent
+               final double totalClimb = totalStats.getMovingAltitudeRange().getClimb(UnitSetLibrary.UNITS_METRES);
+               final double totalDescent = totalStats.getMovingAltitudeRange().getDescent(UnitSetLibrary.UNITS_METRES);
+
+               final double halfDistance = totalDist / 2.0;
+               final double halfClimb = totalClimb / 2.0;
+               final double halfDescent = totalDescent / 2.0;
+
+               // Now loop through points again, looking for the halfway points
+               RangeStats partialStats = new RangeStats();
+               DataPoint prevPoint = null;
+               boolean createdDistance = false, createdClimb = false, createdDescent = false;
+               double prevDistance = 0.0, prevClimb = 0.0, prevDescent = 0.0;
+               for (int i=0; i<numPoints; i++)
+               {
+                       currPoint = _app.getTrackInfo().getTrack().getPoint(i);
+                       partialStats.addPoint(currPoint);
+                       if (!currPoint.isWaypoint())
+                       {
+                               // distance
+                               if (!createdDistance && totalDist > 0.0)
+                               {
+                                       final double currDist = partialStats.getMovingDistanceKilometres();
+                                       createdDistance = processHalfValue(prevPoint, prevDistance, halfDistance,
+                                               currPoint, currDist, HalfwayType.HALF_DISTANCE);
+                                       prevDistance = currDist;
+                               }
+                               // climb
+                               if (!createdClimb && totalClimb > 0.0)
+                               {
+                                       final double currClimb = partialStats.getMovingAltitudeRange().getClimb(UnitSetLibrary.UNITS_METRES);
+                                       createdClimb = processHalfValue(prevPoint, prevClimb, halfClimb,
+                                               currPoint, currClimb, HalfwayType.HALF_CLIMB);
+                                       prevClimb = currClimb;
+                               }
+                               // descent
+                               if (!createdDescent && totalDescent > 0.0)
+                               {
+                                       final double currDescent = partialStats.getMovingAltitudeRange().getDescent(UnitSetLibrary.UNITS_METRES);
+                                       createdDescent = processHalfValue(prevPoint, prevDescent, halfDescent,
+                                               currPoint, currDescent, HalfwayType.HALF_DESCENT);
+                                       prevDescent = currDescent;
+                               }
+
+                               prevPoint = currPoint;
+                       }
+               }
+       }
+
+       /**
+        * Consider a pair of points in the track to see if a new halfway marker should be inserted between them
+        * @param inPrevPoint previous point
+        * @param inPrevValue value of function at this previous point
+        * @param inTargetValue target halfway value
+        * @param inCurrPoint current point
+        * @param inCurrValue value of function at this current point
+        * @param inType type of halfway point
+        */
+       private boolean processHalfValue(DataPoint inPrevPoint, double inPrevValue, double inTargetValue,
+               DataPoint inCurrPoint, double inCurrValue, HalfwayType inType)
+       {
+               if (inPrevValue <= inTargetValue && inCurrValue >= inTargetValue)
+               {
+                       // Calculate position of limit between the two points
+                       final double valueBeforeBreak = inTargetValue - inPrevValue;
+                       final double valueAfterBreak = inCurrValue - inTargetValue;
+                       final double fractionFromPrev = valueBeforeBreak / (valueBeforeBreak + valueAfterBreak);
+                       DataPoint marker = DataPoint.interpolate(inPrevPoint, inCurrPoint, fractionFromPrev);
+                       marker.setFieldValue(Field.WAYPT_NAME, createHalfwayName(inType), false);
+                       _pointsToAdd.add(marker);
+                       return true;
+               }
+               return false;
+       }
+
+       /**
+        * Create the name of the halfway point according to type
+        * @param inType type of point
+        */
+       private String createHalfwayName(HalfwayType inType)
+       {
+               String typeString = null;
+               switch (inType)
+               {
+                       case HALF_DISTANCE:
+                               typeString = "distance";
+                               break;
+                       case HALF_CLIMB:
+                               typeString = "climb";
+                               break;
+                       case HALF_DESCENT:
+                               typeString = "descent";
+                               break;
+               }
+               if (typeString != null)
+               {
+                       return I18nManager.getText("dialog.markers.half." + typeString);
+               }
+               return "half";
+       }
 }
index eaa7b7d8a16ccd8fa42079a3421f6a4c8919ec96..48075e72efe9132ebedc0c640240776d842fd571 100644 (file)
@@ -10,8 +10,7 @@ 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.BorderFactory;
 import javax.swing.ButtonGroup;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
@@ -28,6 +27,7 @@ import tim.prune.data.Field;
 import tim.prune.data.TimeDifference;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.GuiGridLayout;
 import tim.prune.gui.WholeNumberField;
 
 /**
@@ -40,6 +40,10 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
        protected JDialog _dialog = null;
        /** Radio buttons for splitting by distance and time */
        private JRadioButton _distLimitRadio = null, _timeLimitRadio = null;
+       /** Radio button for splitting by fraction (such as half-distance) */
+       private JRadioButton _halvesRadio = null;
+       /** Flag for whether to offer halves or not */
+       private boolean _showHalves = false;
        /** Dropdown for selecting distance units */
        private JComboBox<String> _distUnitsDropdown = null;
        /** Text field for entering distance */
@@ -52,29 +56,28 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
 
 
        /**
-        * React to item changes and key presses
+        * React to item changes and key presses by enabling / disabling ok button
         */
-       private abstract class ChangeListener extends KeyAdapter implements ItemListener
+       private class ChangeListener extends KeyAdapter implements ItemListener
        {
-               /** Method to be implemented */
-               public abstract void optionsChanged();
-
                /** Item changed in ItemListener */
                public void itemStateChanged(ItemEvent arg0) {
-                       optionsChanged();
+                       enableOkButton();
                }
 
                /** Key released in KeyListener */
                public void keyReleased(KeyEvent arg0) {
-                       optionsChanged();
+                       enableOkButton();
                }
        }
 
        /**
         * Constructor
         */
-       public DistanceTimeLimitFunction(App inApp) {
+       public DistanceTimeLimitFunction(App inApp, boolean inShowHalves)
+       {
                super(inApp);
+               _showHalves = inShowHalves;
        }
 
        /**
@@ -112,28 +115,31 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
                JPanel dialogPanel = new JPanel();
                dialogPanel.setLayout(new BorderLayout(5, 5));
 
-               // Make radio buttons for three different options
+               // Make radio buttons for the options
                _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
                _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
+               if (_showHalves) {
+                       _halvesRadio = new JRadioButton(I18nManager.getText("dialog.markers.halves"));
+               }
                ButtonGroup radioGroup = new ButtonGroup();
                radioGroup.add(_distLimitRadio);
                radioGroup.add(_timeLimitRadio);
+               if (_showHalves) {
+                       radioGroup.add(_halvesRadio);
+               }
 
                // 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();
-                       }
-               };
+               limitsPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+               GuiGridLayout grid = new GuiGridLayout(limitsPanel, new double[] {0.5, 1.0},
+                       new boolean[] {false, false});
+               ChangeListener optionsChangedListener = new ChangeListener();
                // distance limits
-               JPanel distLimitPanel = new JPanel();
-               distLimitPanel.setLayout(new FlowLayout());
+               grid.add(_distLimitRadio);
                _distLimitRadio.setSelected(true);
                _distLimitRadio.addItemListener(optionsChangedListener);
-               distLimitPanel.add(_distLimitRadio);
+               JPanel distLimitPanel = new JPanel();
+               distLimitPanel.setLayout(new FlowLayout());
                _distanceField = new WholeNumberField(3);
                _distanceField.addKeyListener(optionsChangedListener);
                distLimitPanel.add(_distanceField);
@@ -143,13 +149,13 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
                _distUnitsDropdown.addItemListener(optionsChangedListener);
                distLimitPanel.add(_distUnitsDropdown);
                distLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               limitsPanel.add(distLimitPanel);
+               grid.add(distLimitPanel);
 
                // time limit panel
+               grid.add(_timeLimitRadio);
+               _timeLimitRadio.addItemListener(optionsChangedListener);
                JPanel timeLimitPanel = new JPanel();
                timeLimitPanel.setLayout(new FlowLayout());
-               _timeLimitRadio.addItemListener(optionsChangedListener);
-               timeLimitPanel.add(_timeLimitRadio);
                _limitHourField = new WholeNumberField(2);
                _limitHourField.addKeyListener(optionsChangedListener);
                timeLimitPanel.add(_limitHourField);
@@ -159,7 +165,14 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
                timeLimitPanel.add(_limitMinField);
                timeLimitPanel.add(new JLabel(I18nManager.getText("units.minutes")));
                timeLimitPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-               limitsPanel.add(timeLimitPanel);
+               grid.add(timeLimitPanel);
+
+               // halves
+               if (_showHalves)
+               {
+                       grid.add(_halvesRadio);
+                       _halvesRadio.addItemListener(optionsChangedListener);
+               }
 
                dialogPanel.add(limitsPanel, BorderLayout.NORTH);
 
@@ -203,6 +216,9 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
                else if (_timeLimitRadio.isSelected()) {
                        enabled = _limitHourField.getValue() > 0 || _limitMinField.getValue() > 0;
                }
+               else if (_halvesRadio != null && _halvesRadio.isSelected()) {
+                       enabled = true;
+               }
                _okButton.setEnabled(enabled);
 
                // Also enable/disable the other fields
@@ -245,6 +261,21 @@ public abstract class DistanceTimeLimitFunction extends GenericFunction
                return 0.0;
        }
 
+       /**
+        * @return selected distance limit in km, or 0.0
+        */
+       protected double getDistanceLimitKilometres()
+       {
+               return Distance.convertRadiansToDistance(getDistanceLimitRadians(), UnitSetLibrary.UNITS_KILOMETRES);
+       }
+
+       /**
+        * @return true if "halves" option was selected
+        */
+       protected boolean isHalvesSelected() {
+               return _halvesRadio != null && _halvesRadio.isSelected();
+       }
+
        /**
         * The dialog has been completed and OK pressed, so do the corresponding function
         */
index 341066a68422160a84a2877155a8c64a1ead516d..7739092831816f238bdc3db92b68eb5b6e24c3e7 100644 (file)
@@ -30,7 +30,8 @@ public class DuplicatePoint extends GenericFunction
        public void begin()
        {
                DataPoint point = _app.getTrackInfo().getCurrentPoint();
-               if (point != null) {
+               if (point != null)
+               {
                        // Pass information back to App to complete function
                        _app.createPoint(point.clonePoint());
                }
diff --git a/src/tim/prune/function/FullRangeDetails.java b/src/tim/prune/function/FullRangeDetails.java
deleted file mode 100644 (file)
index 07ef39e..0000000
+++ /dev/null
@@ -1,367 +0,0 @@
-package tim.prune.function;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import tim.prune.App;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
-import tim.prune.config.Config;
-import tim.prune.data.RangeStats;
-import tim.prune.data.Selection;
-import tim.prune.data.Unit;
-import tim.prune.gui.DisplayUtils;
-import tim.prune.gui.profile.SpeedData;
-
-/**
- * Class to show the full range details in a separate popup
- */
-public class FullRangeDetails extends GenericFunction
-{
-       /** Dialog */
-       private JDialog _dialog = null;
-       /** Label for number of points */
-       private JLabel _numPointsLabel = null;
-       /** Label for number of segments */
-       private JLabel _numSegsLabel = null;
-       /** Label for the maximum speed */
-       private JLabel _maxSpeedLabel = null;
-
-       /** Label for heading of "total" column */
-       private JLabel _colTotalLabel = null;
-       /** Label for heading of "segments" column */
-       private JLabel _colSegmentsLabel = null;
-       /** Labels for distances */
-       private JLabel _totalDistanceLabel = null, _movingDistanceLabel = null;
-       /** Labels for durations */
-       private JLabel _totalDurationLabel = null, _movingDurationLabel = null;
-       /** Labels for climbs */
-       private JLabel _totalClimbLabel = null, _movingClimbLabel = null;
-       /** Labels for descents */
-       private JLabel _totalDescentLabel = null, _movingDescentLabel = null;
-       /** Labels for pace */
-       private JLabel _totalPaceLabel = null, _movingPaceLabel = null;
-       /** Labels for gradient */
-       private JLabel _totalGradientLabel = null, _movingGradientLabel = null;
-       /** Labels for speed */
-       private JLabel _totalSpeedLabel, _movingSpeedLabel = null;
-       /** Labels for vertical speed */
-       private JLabel _totalVertSpeedLabel, _movingVertSpeedLabel = null;
-
-
-       /**
-        * Constructor
-        * @param inApp App object
-        */
-       public FullRangeDetails(App inApp)
-       {
-               super(inApp);
-       }
-
-       /** Get the name key */
-       public String getNameKey() {
-               return "function.fullrangedetails";
-       }
-
-       /**
-        * 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();
-               }
-               updateDetails();
-               _dialog.setVisible(true);
-       }
-
-       /**
-        * Create dialog components
-        * @return Panel containing all gui elements in dialog
-        */
-       private Component makeDialogComponents()
-       {
-               JPanel dialogPanel = new JPanel();
-               dialogPanel.setLayout(new BorderLayout(5, 5));
-               // Label at top
-               JLabel topLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.intro") + ":");
-               topLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
-               dialogPanel.add(topLabel, BorderLayout.NORTH);
-
-               // Details panel in middle
-               JPanel midPanel = new JPanel();
-               midPanel.setLayout(new GridLayout(0, 3, 6, 2));
-               midPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
-               // Number of points
-               JLabel pointsLabel = new JLabel(I18nManager.getText("details.track.points") + ": ");
-               pointsLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(pointsLabel);
-               _numPointsLabel = new JLabel("100");
-               midPanel.add(_numPointsLabel);
-               midPanel.add(new JLabel(" "));
-               // Number of segments
-               JLabel segLabel = new JLabel(I18nManager.getText("details.range.numsegments") + ": ");
-               segLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(segLabel);
-               _numSegsLabel = new JLabel("100");
-               midPanel.add(_numSegsLabel);
-               midPanel.add(new JLabel(" "));
-               // Maximum speed
-               JLabel maxSpeedLabel = new JLabel(I18nManager.getText("details.range.maxspeed") + ": ");
-               maxSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(maxSpeedLabel);
-               _maxSpeedLabel = new JLabel("10 km/h");
-               midPanel.add(_maxSpeedLabel);
-               midPanel.add(new JLabel(" "));
-
-               // blank row
-               for (int i=0; i<3; i++) midPanel.add(new JLabel(" "));
-
-               // Row for column headings
-               midPanel.add(new JLabel(" "));
-               _colTotalLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.coltotal"));
-               midPanel.add(_colTotalLabel);
-               _colSegmentsLabel = new JLabel(I18nManager.getText("dialog.fullrangedetails.colsegments"));
-               midPanel.add(_colSegmentsLabel);
-
-               // Distance
-               JLabel distLabel = new JLabel(I18nManager.getText("fieldname.distance") + ": ");
-               distLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(distLabel);
-               _totalDistanceLabel = new JLabel("5 km");
-               midPanel.add(_totalDistanceLabel);
-               _movingDistanceLabel = new JLabel("5 km");
-               midPanel.add(_movingDistanceLabel);
-
-               // Duration
-               JLabel durationLabel = new JLabel(I18nManager.getText("fieldname.duration") + ": ");
-               durationLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(durationLabel);
-               _totalDurationLabel = new JLabel("15 min");
-               midPanel.add(_totalDurationLabel);
-               _movingDurationLabel = new JLabel("15 min");
-               midPanel.add(_movingDurationLabel);
-
-               // Speed
-               JLabel speedLabel = new JLabel(I18nManager.getText("details.range.avespeed") + ": ");
-               speedLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(speedLabel);
-               _totalSpeedLabel = new JLabel("5.5 km/h");
-               midPanel.add(_totalSpeedLabel);
-               _movingSpeedLabel = new JLabel("5.5 km/h");
-               midPanel.add(_movingSpeedLabel);
-
-               // Pace
-               JLabel paceLabel = new JLabel(I18nManager.getText("details.range.pace") + ": ");
-               paceLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(paceLabel);
-               _totalPaceLabel = new JLabel("8 min/km");
-               midPanel.add(_totalPaceLabel);
-               _movingPaceLabel = new JLabel("8 min/km");
-               midPanel.add(_movingPaceLabel);
-
-               // Climb
-               JLabel climbLabel = new JLabel(I18nManager.getText("details.range.climb") + ": ");
-               climbLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(climbLabel);
-               _totalClimbLabel = new JLabel("1000 m");
-               midPanel.add(_totalClimbLabel);
-               _movingClimbLabel = new JLabel("1000 m");
-               midPanel.add(_movingClimbLabel);
-               // Descent
-               JLabel descentLabel = new JLabel(I18nManager.getText("details.range.descent") + ": ");
-               descentLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(descentLabel);
-               _totalDescentLabel = new JLabel("1000 m");
-               midPanel.add(_totalDescentLabel);
-               _movingDescentLabel = new JLabel("1000 m");
-               midPanel.add(_movingDescentLabel);
-
-               // Gradient
-               JLabel gradientLabel = new JLabel(I18nManager.getText("details.range.gradient") + ": ");
-               gradientLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(gradientLabel);
-               _totalGradientLabel = new JLabel("10 %");
-               midPanel.add(_totalGradientLabel);
-               _movingGradientLabel = new JLabel("10 %");
-               midPanel.add(_movingGradientLabel);
-
-               // Vertical speed
-               JLabel vSpeedLabel = new JLabel(I18nManager.getText("fieldname.verticalspeed") + ": ");
-               vSpeedLabel.setHorizontalAlignment(JLabel.RIGHT);
-               midPanel.add(vSpeedLabel);
-               _totalVertSpeedLabel = new JLabel("1 m/s");
-               midPanel.add(_totalVertSpeedLabel);
-               _movingVertSpeedLabel = new JLabel("1 m/s");
-               midPanel.add(_movingVertSpeedLabel);
-
-               dialogPanel.add(midPanel, BorderLayout.CENTER);
-               // button panel at bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               JButton closeButton = new JButton(I18nManager.getText("button.close"));
-               closeButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e)
-                       {
-                               _dialog.dispose();
-                       }
-               });
-               closeButton.addKeyListener(new KeyAdapter() {
-                       public void keyPressed(KeyEvent inE) {
-                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
-                               super.keyPressed(inE);
-                       }
-               });
-               buttonPanel.add(closeButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               return dialogPanel;
-       }
-
-
-       /**
-        * Update the labels with the current details
-        */
-       private void updateDetails()
-       {
-               Selection selection = _app.getTrackInfo().getSelection();
-               // Do the calculations with a separate class
-               RangeStats stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
-
-               // Number of points
-               _numPointsLabel.setText("" + stats.getNumPoints());
-               // Number of segments
-               _numSegsLabel.setText("" + stats.getNumSegments());
-               final boolean isMultiSegments = (stats.getNumSegments() > 1);
-               // Set visibility of third column accordingly
-               _movingDistanceLabel.setVisible(isMultiSegments);
-               _movingDurationLabel.setVisible(isMultiSegments || stats.getTimestampsOutOfSequence());
-               // FIXME: What to show if timestamps are out of sequence? Warning message?
-               _movingClimbLabel.setVisible(isMultiSegments);
-               _movingDescentLabel.setVisible(isMultiSegments);
-               _movingSpeedLabel.setVisible(isMultiSegments);
-               _movingPaceLabel.setVisible(isMultiSegments);
-               _movingGradientLabel.setVisible(isMultiSegments);
-               _movingVertSpeedLabel.setVisible(isMultiSegments);
-
-               // Total and moving distance in current units
-               final Unit distUnit = Config.getUnitSet().getDistanceUnit();
-               final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
-               _totalDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getTotalDistance()) + " " + distUnitsStr);
-               _movingDistanceLabel.setText(DisplayUtils.roundedNumber(stats.getMovingDistance()) + " " + distUnitsStr);
-
-               // Duration
-               _totalDurationLabel.setText(DisplayUtils.buildDurationString(stats.getTotalDurationInSeconds()));
-               _movingDurationLabel.setText(DisplayUtils.buildDurationString(stats.getMovingDurationInSeconds()));
-
-               // Climb and descent
-               final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
-               final String altUnitsStr = " " + I18nManager.getText(altUnit.getShortnameKey());
-               if (stats.getTotalAltitudeRange().hasRange()) {
-                       _totalClimbLabel.setText(stats.getTotalAltitudeRange().getClimb(altUnit) + altUnitsStr);
-                       _totalDescentLabel.setText(stats.getTotalAltitudeRange().getDescent(altUnit) + altUnitsStr);
-               }
-               else {
-                       _totalClimbLabel.setText("");
-                       _totalDescentLabel.setText("");
-               }
-               if (stats.getMovingAltitudeRange().hasRange()) {
-                       _movingClimbLabel.setText(stats.getMovingAltitudeRange().getClimb(altUnit) + altUnitsStr);
-                       _movingDescentLabel.setText(stats.getMovingAltitudeRange().getDescent(altUnit) + altUnitsStr);
-               }
-               else {
-                       _movingClimbLabel.setText("");
-                       _movingDescentLabel.setText("");
-               }
-
-               // Overall pace and speed
-               final String speedUnitsStr = I18nManager.getText(Config.getUnitSet().getSpeedUnit().getShortnameKey());
-               long numSecs = stats.getTotalDurationInSeconds();
-               double dist = stats.getTotalDistance();
-               if (numSecs > 0 && dist > 0)
-               {
-                       _totalSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
-                       _totalPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
-                               + " / " + distUnitsStr);
-               }
-               else {
-                       _totalSpeedLabel.setText("");
-                       _totalPaceLabel.setText("");
-               }
-               // and same for within the segments
-               numSecs = stats.getMovingDurationInSeconds();
-               dist = stats.getMovingDistance();
-               if (numSecs > 0 && dist > 0)
-               {
-                       _movingSpeedLabel.setText(DisplayUtils.roundedNumber(dist/numSecs*3600.0) + " " + speedUnitsStr);
-                       _movingPaceLabel.setText(DisplayUtils.buildDurationString((long) (numSecs/dist))
-                               + " / " + distUnitsStr);
-               }
-               else {
-                       _movingSpeedLabel.setText("");
-                       _movingPaceLabel.setText("");
-               }
-
-               // Gradient
-               if (stats.getTotalAltitudeRange().hasRange()) {
-                       _totalGradientLabel.setText(DisplayUtils.formatOneDp(stats.getTotalGradient()) + " %");
-               }
-               else {
-                       _totalGradientLabel.setText("");
-               }
-               if (stats.getMovingAltitudeRange().hasRange()) {
-                       _movingGradientLabel.setText(DisplayUtils.formatOneDp(stats.getMovingGradient()) + " %");
-               }
-               else {
-                       _movingGradientLabel.setText("");
-               }
-
-               // Maximum speed
-               SpeedData speeds = new SpeedData(_app.getTrackInfo().getTrack());
-               speeds.init(Config.getUnitSet());
-               double maxSpeed = 0.0;
-               for (int i=selection.getStart(); i<=selection.getEnd(); i++)
-               {
-                       if (speeds.hasData(i) && (speeds.getData(i) > maxSpeed)) {
-                               maxSpeed = speeds.getData(i);
-                       }
-               }
-               if (maxSpeed > 0.0) {
-                       _maxSpeedLabel.setText(DisplayUtils.roundedNumber(maxSpeed) + " " + speedUnitsStr);
-               }
-               else {
-                       _maxSpeedLabel.setText("");
-               }
-
-               // vertical speed
-               final String vertSpeedUnitsStr = I18nManager.getText(Config.getUnitSet().getVerticalSpeedUnit().getShortnameKey());
-               if (stats.getMovingAltitudeRange().hasRange() && stats.getTotalDurationInSeconds() > 0)
-               {
-                       // got an altitude and time - do totals
-                       _totalVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getTotalVerticalSpeed()) + " " + vertSpeedUnitsStr);
-                       _movingVertSpeedLabel.setText(DisplayUtils.roundedNumber(stats.getMovingVerticalSpeed()) + " " + vertSpeedUnitsStr);
-               }
-               else
-               {
-                       // no vertical speed available
-                       _totalVertSpeedLabel.setText("");
-                       _movingVertSpeedLabel.setText("");
-               }
-       }
-}
index 221fdaf989c6c62141d0dfbe7ac29cfb66b069f0..ce0dad59faa1603d2831c8cbe2f6579dfb120dfe 100644 (file)
@@ -122,7 +122,8 @@ public class InterpolateFunction extends SingleNumericParameterFunction
                // Replace track with new points array
                if (track.replaceContents(newPoints))
                {
-                       _app.completeFunction(undo, I18nManager.getText("confirm.interpolate"));
+                       final String confirmMessage = I18nManager.getTextWithNumber("confirm.pointsadded", totalInserted);
+                       _app.completeFunction(undo, confirmMessage);
                        // Alter selection
                        _app.getTrackInfo().getSelection().selectRange(startIndex, endIndex + totalInserted);
                }
diff --git a/src/tim/prune/function/PasteCoordinateList.java b/src/tim/prune/function/PasteCoordinateList.java
new file mode 100644 (file)
index 0000000..9fc259e
--- /dev/null
@@ -0,0 +1,147 @@
+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.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.load.TextFileLoader;
+
+/**
+ * Class to provide the function to paste a list of coordinates
+ * and create points for them as if they were loaded from a text file
+ */
+public class PasteCoordinateList extends GenericFunction
+{
+       private JDialog _dialog = null;
+       private JTextArea _coordArea = null;
+       private JButton _okButton = null;
+
+
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        */
+       public PasteCoordinateList(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.pastecoordinatelist";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               // MAYBE: Paste clipboard into the edit area
+               _coordArea.setText("");
+               enableOK();
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(0, 10));
+               dialogPanel.add(new JLabel(I18nManager.getText("dialog.pastecoordinatelist.desc")), BorderLayout.NORTH);
+               _coordArea = new JTextArea(8, 35);
+               _coordArea.setLineWrap(true);
+               _coordArea.setWrapStyleWord(true);
+               JScrollPane coordsPane = new JScrollPane(_coordArea);
+               // Listeners to enable/disable ok button
+               KeyAdapter keyListener = new KeyAdapter() {
+                       /** Key released */
+                       public void keyReleased(KeyEvent inE) {
+                               enableOK();
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               MouseAdapter mouseListener = new MouseAdapter() {
+                       public void mouseReleased(MouseEvent inE) {
+                               enableOK();
+                       }
+               };
+               _coordArea.addKeyListener(keyListener);
+               _coordArea.addMouseListener(mouseListener);
+               dialogPanel.add(coordsPane, BorderLayout.CENTER);
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               if (_okButton.isEnabled()) {finish();}
+                       }
+               };
+               _okButton.addActionListener(okListener);
+               _okButton.setEnabled(false);
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       /**
+        * Enable or disable the OK button based on the contents of the text field
+        */
+       private void enableOK()
+       {
+               String text = _coordArea.getText();
+               _okButton.setEnabled(text != null && text.length() > 6
+                       && (text.indexOf(' ') >= 0 || text.indexOf(',') >= 0));
+       }
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               new TextFileLoader(_app, _parentFrame).loadText(_coordArea.getText());
+               _dialog.dispose();
+       }
+}
index c9b2ad1c7ff3f74da6e8162fbb84d8fa886c1e42..c60bb854afe03cabd19189495b38399ba2f0c06d 100644 (file)
@@ -183,7 +183,8 @@ public class PasteCoordinates extends GenericFunction
                else if (items.length == 3) {
                        point = parseValues(items[0].trim(), items[1].trim(), items[2].trim());
                }
-               else {
+               else
+               {
                        // Splitting with commas didn't work, so try spaces
                        items = _coordField.getText().split(" ");
                        if (items.length == 2) {
@@ -211,7 +212,8 @@ public class PasteCoordinates extends GenericFunction
                                I18nManager.getText("dialog.pastecoordinates.nothingfound"),
                                I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
                }
-               else {
+               else
+               {
                        // See if name was entered
                        String name = _nameField.getText();
                        if (name != null && name.length() > 0) {
index 2ac71e93b6e38bbbaab727aa1a6d7d421c4a117a..44b77d9bc3ca7827081ff48f86a53c3143f9e27e 100644 (file)
@@ -1,11 +1,11 @@
 package tim.prune.function;
 
+import java.awt.Desktop;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
 
 import javax.sound.sampled.AudioInputStream;
 import javax.sound.sampled.AudioSystem;
@@ -146,17 +146,10 @@ public class PlayAudioFunction extends GenericFunction implements Runnable
                {
                        try
                        {
-                               Class<?> d = Class.forName("java.awt.Desktop");
-                               d.getDeclaredMethod("open", new Class[] {File.class}).invoke(
-                                       d.getDeclaredMethod("getDesktop").invoke(null), new Object[] {inFile});
-                               //above code mimics: Desktop.getDesktop().open(audioFile);
+                               Desktop.getDesktop().open(inFile);
                                played = true;
                        }
-                       catch (InvocationTargetException e) {
-                               System.err.println("ITE: " + e.getCause().getClass().getName() + " - " + e.getCause().getMessage());
-                               played = false;
-                       }
-                       catch (Exception ignore) {
+                       catch (IOException ignore) {
                                System.err.println(ignore.getClass().getName() + " - " + ignore.getMessage());
                                played = false;
                        }
diff --git a/src/tim/prune/function/PlusCodeFunction.java b/src/tim/prune/function/PlusCodeFunction.java
new file mode 100644 (file)
index 0000000..b208b7f
--- /dev/null
@@ -0,0 +1,209 @@
+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.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+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.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.Field;
+import tim.prune.function.olc.OlcArea;
+import tim.prune.function.olc.OlcDecoder;
+import tim.prune.gui.GuiGridLayout;
+
+/**
+ * Class to provide the function to parse
+ * OpenLocationCodes, or PlusCodes
+ */
+public class PlusCodeFunction extends GenericFunction
+{
+       private JDialog _dialog = null;
+       private JTextField _codeField = null;
+       private JButton _okButton = null;
+
+
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        */
+       public PlusCodeFunction(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.enterpluscode";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+               // MAYBE: Paste clipboard into the edit field
+               _codeField.setText("");
+               enableOK();
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(0, 10));
+               dialogPanel.add(new JLabel(I18nManager.getText("dialog.pluscode.desc")), BorderLayout.NORTH);
+               JPanel mainPanel = new JPanel();
+               GuiGridLayout grid = new GuiGridLayout(mainPanel);
+               _codeField = new JTextField("", 12);
+               // Listeners to enable/disable ok button
+               KeyAdapter keyListener = new KeyAdapter() {
+                       /** Key released */
+                       public void keyReleased(KeyEvent inE) {
+                               enableOK();
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               MouseAdapter mouseListener = new MouseAdapter() {
+                       public void mouseReleased(MouseEvent inE) {
+                               enableOK();
+                       }
+               };
+               _codeField.addKeyListener(keyListener);
+               _codeField.addMouseListener(mouseListener);
+               JLabel codeLabel = new JLabel(I18nManager.getText("dialog.pluscode.code"));
+               codeLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               grid.add(codeLabel);
+               grid.add(_codeField);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               if (_okButton.isEnabled()) {finish();}
+                       }
+               };
+               _okButton.addActionListener(okListener);
+               _okButton.setEnabled(false);
+               _codeField.addActionListener(okListener);
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       /**
+        * Enable or disable the OK button based on the contents of the text field
+        */
+       private void enableOK()
+       {
+               String text = _codeField.getText();
+               _okButton.setEnabled(text != null && text.length() > 7
+                       && text.indexOf(' ') < 0 && text.indexOf(',') < 0);
+       }
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               OlcArea area = OlcDecoder.decode(_codeField.getText());
+
+               if (area == null)
+               {
+                       JOptionPane.showMessageDialog(_parentFrame,
+                               I18nManager.getText("dialog.pluscode.nothingfound"),
+                               I18nManager.getText(getNameKey()), JOptionPane.ERROR_MESSAGE);
+               }
+               else if (loadTrack(area))
+               {
+                       _dialog.dispose();
+               }
+       }
+
+       /**
+        * Load the generated points from the given area
+        * @param inArea rectangular area
+        * @return true on success
+        */
+       private boolean loadTrack(OlcArea inArea)
+       {
+               if (inArea == null)
+               {
+                       return false;
+               }
+
+               final Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.WAYPT_NAME};
+               _app.autoAppendNextFile();
+
+               if (inArea.minLat == inArea.maxLat && inArea.minLon == inArea.maxLon)
+               {
+                       String[][] pointData = new String[1][];
+                       pointData[0] = new String[3]; // lat, long, name
+                       pointData[0][0] = "" + inArea.minLat;
+                       pointData[0][1] = "" + inArea.minLon;
+                       pointData[0][2] = _codeField.getText();
+                       _app.informDataLoaded(fields, pointData, null, null);
+               }
+               else
+               {
+                       String[][] pointData = new String[6][];
+                       for (int i=0; i<5; i++)
+                       {
+                               pointData[i] = new String[3]; // lat, long, name
+                               pointData[i][0] = "" + ((i%4==0 || i==3) ? inArea.minLat : inArea.maxLat);
+                               pointData[i][1] = "" + ((i%4==0 || i==1) ? inArea.minLon : inArea.maxLon);
+                               pointData[i][2] = null;
+                       }
+                       // Middle point with name
+                       pointData[5] = new String[3]; // lat, long, name
+                       pointData[5][0] = "" + ((inArea.minLat + inArea.maxLat) / 2.0);
+                       pointData[5][1] = "" + ((inArea.minLon + inArea.maxLon) / 2.0);
+                       pointData[5][2] = _codeField.getText();
+                       _app.informDataLoaded(fields, pointData, null, null);
+               }
+               return true;
+       }
+}
diff --git a/src/tim/prune/function/ProjectPoint.java b/src/tim/prune/function/ProjectPoint.java
new file mode 100644 (file)
index 0000000..10c0675
--- /dev/null
@@ -0,0 +1,225 @@
+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.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.config.Config;
+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.data.Unit;
+import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.DecimalNumberField;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.WholeNumberField;
+
+
+/**
+ * Class to provide the function to project the current point
+ * with a given bearing and distance
+ */
+public class ProjectPoint extends GenericFunction
+{
+       private JDialog _dialog = null;
+       private WholeNumberField _bearingField = null;
+       private JLabel _distanceDescLabel = null;
+       private DecimalNumberField _distanceField = null;
+       private boolean _distanceIsMetric = true;
+       private JTextField _nameField = null;
+       private JButton _okButton = null;
+
+
+       /**
+        * Constructor
+        * @param inApp application object for callback
+        */
+       public ProjectPoint(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.projectpoint";
+       }
+
+       /**
+        * Begin the function
+        */
+       public void begin()
+       {
+               // Make dialog window
+               if (_dialog == null)
+               {
+                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+                       _dialog.setLocationRelativeTo(_parentFrame);
+                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+                       _dialog.getContentPane().add(makeDialogComponents());
+                       _dialog.pack();
+               }
+
+               // Clear fields
+               _bearingField.setText("");
+               _distanceField.setText("");
+               _nameField.setText("");
+               // Set the units of the distance label
+               setLabelText();
+               enableOK();
+               _dialog.setVisible(true);
+       }
+
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel dialogPanel = new JPanel();
+               dialogPanel.setLayout(new BorderLayout(0, 10));
+               dialogPanel.add(new JLabel(I18nManager.getText("dialog.projectpoint.desc")), BorderLayout.NORTH);
+               JPanel mainPanel = new JPanel();
+               GuiGridLayout grid = new GuiGridLayout(mainPanel);
+               _bearingField = new WholeNumberField(3);
+               _distanceField = new DecimalNumberField(false);
+               // Listeners to enable/disable ok button
+               KeyAdapter keyListener = new KeyAdapter() {
+                       /** Key released */
+                       public void keyReleased(KeyEvent inE) {
+                               enableOK();
+                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                                       _dialog.dispose();
+                               }
+                       }
+               };
+               MouseAdapter mouseListener = new MouseAdapter() {
+                       public void mouseReleased(MouseEvent inE) {
+                               enableOK();
+                       }
+               };
+               _bearingField.addKeyListener(keyListener);
+               _bearingField.addMouseListener(mouseListener);
+               _distanceField.addKeyListener(keyListener);
+               _distanceField.addMouseListener(mouseListener);
+
+               JLabel bearingLabel = new JLabel(I18nManager.getText("dialog.projectpoint.bearing"));
+               bearingLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               grid.add(bearingLabel);
+               grid.add(_bearingField);
+
+               // Distance including units
+               _distanceDescLabel = new JLabel(I18nManager.getText("fieldname.distance") + " (ft)");
+               // Note, this label will be reset at each run
+               _distanceDescLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               grid.add(_distanceDescLabel);
+               grid.add(_distanceField);
+
+               // Waypoint name
+               JLabel nameLabel = new JLabel(I18nManager.getText("dialog.pointnameedit.name"));
+               nameLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+               grid.add(nameLabel);
+               _nameField = new JTextField("", 12);
+               grid.add(_nameField);
+               dialogPanel.add(mainPanel, BorderLayout.CENTER);
+               // button panel at bottom
+               JPanel buttonPanel = new JPanel();
+               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               ActionListener okListener = new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               if (_okButton.isEnabled()) {finish();}
+                       }
+               };
+               _okButton.addActionListener(okListener);
+               _okButton.setEnabled(false);
+
+               buttonPanel.add(_okButton);
+               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               buttonPanel.add(cancelButton);
+               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+               return dialogPanel;
+       }
+
+       /**
+        * Set the label text according to the current units
+        */
+       private void setLabelText()
+       {
+               Unit distUnit = Config.getUnitSet().getDistanceUnit();
+               _distanceIsMetric = (distUnit == UnitSetLibrary.UNITS_METRES || distUnit == UnitSetLibrary.UNITS_KILOMETRES);
+               distUnit = _distanceIsMetric ? UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET;
+               final String unitKey = distUnit.getShortnameKey();
+               _distanceDescLabel.setText(I18nManager.getText("fieldname.distance") + " (" + I18nManager.getText(unitKey) + ")");
+       }
+
+       /**
+        * Enable or disable the OK button based on the contents of the input fields
+        */
+       private void enableOK()
+       {
+               final boolean bearingOk = !_bearingField.getText().isEmpty()
+                       && _bearingField.getValue() < 360;
+               final boolean distanceOk = _distanceField.getValue() > 0.0;
+               _okButton.setEnabled(bearingOk && distanceOk);
+       }
+
+       /**
+        * Finish the dialog when OK pressed
+        */
+       private void finish()
+       {
+               DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
+               Unit distUnit = _distanceIsMetric ? UnitSetLibrary.UNITS_METRES : UnitSetLibrary.UNITS_FEET;
+               final double projectRads = Distance.convertDistanceToRadians(_distanceField.getValue(), distUnit);
+               final double origLatRads = Math.toRadians(currPoint.getLatitude().getDouble());
+               final double origLonRads = Math.toRadians(currPoint.getLongitude().getDouble());
+               System.out.println("Project from: " + origLatRads + ", " + origLonRads);
+               final double bearingRads = Math.toRadians(_bearingField.getValue());
+
+               double lat2 = Math.asin(Math.sin(origLatRads) * Math.cos(projectRads)
+                       + Math.cos(origLatRads) * Math.sin(projectRads) * Math.cos(bearingRads));
+               double lon2 = origLonRads + Math.atan2(Math.sin(bearingRads) * Math.sin(projectRads) * Math.cos(origLatRads),
+                       Math.cos(projectRads) - Math.sin(origLatRads) * Math.sin(lat2));
+
+               double finalLatDeg = Math.toDegrees(lat2);
+               double finalLonDeg = Math.toDegrees(lon2);
+               System.out.println("Result is: lat=" + finalLatDeg + ", lon=" + finalLonDeg);
+
+               // Create point and append to track
+               DataPoint point = new DataPoint(new Latitude(finalLatDeg, Coordinate.FORMAT_DEG),
+                       new Longitude(finalLonDeg, Coordinate.FORMAT_DEG), null);
+               point.setFieldValue(Field.WAYPT_NAME, _nameField.getText(), false);
+               _app.createPoint(point);
+
+               _dialog.dispose();
+       }
+}
index 17e937027c2d021593979e7c54fefcdf4851f940..c62b13097875c1485209feb243fa623b69b767d0 100644 (file)
@@ -119,7 +119,7 @@ public class RearrangeWaypointsFunction extends RearrangeFunction
                // Exit if the data is already in the specified order
                final boolean wpsToStart = (inRearrangeOption == Rearrange.TO_START);
                final boolean doSort = (inSortOption != SortMode.DONT_SORT);
-               if (numWaypoints == 0 || numNonWaypoints == 0
+               if (numWaypoints == 0
                        || (wpsToStart && !wayAfterNon && nonAfterWay && !doSort)
                        || (!wpsToStart && wayAfterNon && !nonAfterWay && !doSort)
                        || inRearrangeOption == Rearrange.TO_NEAREST)
index 20e2fc81d4de00698f7265c4651212bf71ff740d..39df30a6657050ee8b50f9485f6e19679eaf38af 100644 (file)
@@ -84,7 +84,7 @@ public class SearchOpenCachingDeFunction extends GenericDownloaderFunction
        private void submitSearch(double inLat, double inLon)
        {
                // The only parameters are lat and long from the current point
-               String urlString = "http://opencaching.de/search.php?searchto=searchbydistance&showresult=1"
+               String urlString = "https://opencaching.de/search.php?searchto=searchbydistance&showresult=1"
                        + "&output=XML&sort=bydistance&lat=" + inLat
                        + "&lon=" + inLon + "&distance=" + MAX_DISTANCE + "&unit=km";
                // Parse the returned XML with a special handler
diff --git a/src/tim/prune/function/ShowFullDetails.java b/src/tim/prune/function/ShowFullDetails.java
new file mode 100644 (file)
index 0000000..8f8289e
--- /dev/null
@@ -0,0 +1,417 @@
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.TimeZone;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+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;
+import tim.prune.data.DataPoint;
+import tim.prune.data.Field;
+import tim.prune.data.Photo;
+import tim.prune.data.RangeStatsWithGradients;
+import tim.prune.data.Selection;
+import tim.prune.data.SpeedCalculator;
+import tim.prune.data.SpeedValue;
+import tim.prune.data.Track;
+import tim.prune.data.Unit;
+import tim.prune.data.UnitSet;
+import tim.prune.gui.CoordDisplay;
+import tim.prune.gui.DisplayUtils;
+
+
+/**
+ * Class to show the full point/range details in a separate popup
+ */
+public class ShowFullDetails extends GenericFunction
+{
+       private JDialog _dialog = null;
+       private JTabbedPane _tabs = null;
+       private JButton _okButton = null;
+
+       private JTextArea _pointTextArea = null;
+       private JTextArea _rangeTextArea = null;
+
+
+       /**
+        * Constructor
+        * @param inApp App object
+        */
+       public ShowFullDetails(App inApp)
+       {
+               super(inApp);
+       }
+
+       /** Get the name key */
+       public String getNameKey() {
+               return "function.viewfulldetails";
+       }
+
+       /**
+        * 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();
+               }
+               updateDetails();
+               _dialog.setVisible(true);
+               _okButton.requestFocus();
+       }
+
+       /**
+        * Create dialog components
+        * @return Panel containing all gui elements in dialog
+        */
+       private Component makeDialogComponents()
+       {
+               JPanel mainPanel = new JPanel();
+               mainPanel.setLayout(new BorderLayout());
+
+               _tabs = new JTabbedPane();
+               mainPanel.add(_tabs, BorderLayout.CENTER);
+
+               JPanel pointPanel = new JPanel();
+               pointPanel.setLayout(new BorderLayout());
+               _pointTextArea = new JTextArea(I18nManager.getText("details.nopointselection"));
+               _pointTextArea.setEditable(false);
+               _pointTextArea.setLineWrap(true);
+               JScrollPane scrollPane = new JScrollPane(_pointTextArea);
+               scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+               scrollPane.setPreferredSize(new Dimension(500, 230));
+               pointPanel.add(scrollPane, BorderLayout.CENTER);
+               _tabs.add(I18nManager.getText("details.pointdetails"), pointPanel);
+
+               JPanel rangePanel = new JPanel();
+               rangePanel.setLayout(new BorderLayout());
+               _rangeTextArea = new JTextArea(I18nManager.getText("details.norangeselection"));
+               _rangeTextArea.setEditable(false);
+               _rangeTextArea.setLineWrap(true);
+               JScrollPane scrollPane2 = new JScrollPane(_rangeTextArea);
+               scrollPane2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+               scrollPane2.setPreferredSize(new Dimension(500, 230));
+               rangePanel.add(scrollPane2, BorderLayout.CENTER);
+               _tabs.add(I18nManager.getText("details.rangedetails"), rangePanel);
+
+               // OK button at the bottom
+               JPanel okPanel = new JPanel();
+               okPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+               _okButton = new JButton(I18nManager.getText("button.ok"));
+               _okButton.addActionListener(new ActionListener()
+               {
+                       public void actionPerformed(ActionEvent e)
+                       {
+                               _dialog.dispose();
+                       }
+               });
+               _okButton.addKeyListener(new KeyListener() {
+                       public void keyPressed(KeyEvent e)
+                       {
+                               if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+                       }
+                       public void keyTyped(KeyEvent e) {}
+                       public void keyReleased(KeyEvent e) {}
+               });
+               okPanel.add(_okButton);
+               mainPanel.add(okPanel, BorderLayout.SOUTH);
+               return mainPanel;
+       }
+
+
+       /**
+        * Update the labels with the current details
+        */
+       private void updateDetails()
+       {
+               if (_app.getTrackInfo().getCurrentPoint() != null)
+               {
+                       final String pointString = makePointDescription(_app.getTrackInfo().getTrack(),
+                               _app.getTrackInfo().getSelection().getCurrentPointIndex());
+                       _pointTextArea.setText(pointString);
+                       // Select point tab
+                       _tabs.setSelectedIndex(0);
+               }
+               else
+               {
+                       _pointTextArea.setText(I18nManager.getText("details.nopointselection"));
+                       // Select range tab
+                       _tabs.setSelectedIndex(1);
+               }
+
+               Selection selection = _app.getTrackInfo().getSelection();
+               if (selection.hasRangeSelected())
+               {
+                       RangeStatsWithGradients stats = new RangeStatsWithGradients(_app.getTrackInfo().getTrack(),
+                               selection.getStart(), selection.getEnd());
+                       SpeedValue maxSpeed = calculateMaxSpeed(_app.getTrackInfo().getTrack(),
+                               selection.getStart(), selection.getEnd());
+                       _rangeTextArea.setText(makeRangeDescription(stats, maxSpeed));
+               }
+               else
+               {
+                       _rangeTextArea.setText(I18nManager.getText("details.norangeselection"));
+               }
+       }
+
+       /**
+        * Calculate the maximum horizontal speed value in the given selection
+        * @param inTrack track object
+        * @param inStartIndex start of selection
+        * @param inEndIndex end of selection
+        * @return max speed, if any
+        */
+       private static SpeedValue calculateMaxSpeed(Track inTrack, int inStartIndex, int inEndIndex)
+       {
+               SpeedValue maxSpeed = new SpeedValue();
+               SpeedValue currSpeed = new SpeedValue();
+               for (int i=inStartIndex; i<=inEndIndex; i++)
+               {
+                       SpeedCalculator.calculateSpeed(inTrack, i, currSpeed);
+                       if (currSpeed.isValid() && (!maxSpeed.isValid() || currSpeed.getValue() > maxSpeed.getValue()))
+                       {
+                               maxSpeed.setValue(currSpeed.getValue());
+                       }
+               }
+               return maxSpeed;
+       }
+
+       /**
+        * @param inTrack current track
+        * @param inPointIndex current point index
+        * @return string describing point details
+        */
+       private static String makePointDescription(Track inTrack, int inPointIndex)
+       {
+               DataPoint point = inTrack.getPoint(inPointIndex);
+               if (point == null)
+               {
+                       return "";
+               }
+
+               final int coordDisplayFormat = Coordinate.getCoordinateFormatForDisplay(
+                       Config.getConfigInt(Config.KEY_COORD_DISPLAY_FORMAT));
+               StringBuffer result = new StringBuffer();
+               final String latStr = CoordDisplay.makeCoordinateLabel(point.getLatitude(), coordDisplayFormat);
+               final String lonStr = CoordDisplay.makeCoordinateLabel(point.getLongitude(), coordDisplayFormat);
+               addTextPair(result, "fieldname.latitude", latStr);
+               addTextPair(result, "fieldname.longitude", lonStr);
+               addTextPair(result, "fieldname.coordinates", latStr + ", " + lonStr);
+
+               if (point.hasAltitude())
+               {
+                       final Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+                       addTextPair(result, "fieldname.altitude", "" + point.getAltitude().getValue(altUnit),
+                               I18nManager.getText(altUnit.getShortnameKey()));
+               }
+               if (point.hasTimestamp())
+               {
+                       TimeZone timezone = TimezoneHelper.getSelectedTimezone();
+                       addTextPair(result, "fieldname.date", point.getTimestamp().getDateText(timezone));
+                       addTextPair(result, "fieldname.timestamp", point.getTimestamp().getTimeText(timezone));
+               }
+
+               addTextPair(result, "fieldname.waypointname", point.getWaypointName());
+
+               addTextPair(result, "fieldname.description", point.getFieldValue(Field.DESCRIPTION));
+
+               addTextPair(result, "fieldname.waypointtype", point.getFieldValue(Field.WAYPT_TYPE));
+
+               // Speed can come from either timestamps and distances, or speed values in data
+               SpeedValue speedValue = new SpeedValue();
+               SpeedCalculator.calculateSpeed(inTrack, inPointIndex, speedValue);
+               UnitSet unitSet = Config.getUnitSet();
+               if (speedValue.isValid())
+               {
+                       final String speedUnitsStr = I18nManager.getText(unitSet.getSpeedUnit().getShortnameKey());
+                       String speed = DisplayUtils.roundedNumber(speedValue.getValue());
+                       addTextPair(result, "fieldname.speed", speed, speedUnitsStr);
+               }
+
+               // Now do the vertical speed in the same way
+               SpeedCalculator.calculateVerticalSpeed(inTrack, inPointIndex, speedValue);
+               if (speedValue.isValid())
+               {
+                       final String vSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
+                       String speed = DisplayUtils.roundedNumber(speedValue.getValue());
+                       addTextPair(result, "fieldname.verticalspeed", speed, vSpeedUnitsStr);
+               }
+
+               Photo currentPhoto = point.getPhoto();
+               if (currentPhoto != null)
+               {
+                       addTextPair(result, "details.photofile", currentPhoto.getName());
+                       addTextPair(result, "details.media.fullpath", currentPhoto.getFullPath());
+               }
+
+               AudioClip currentAudio = point.getAudio();
+               if (currentAudio != null)
+               {
+                       addTextPair(result, "details.audio.file", currentAudio.getName());
+                       addTextPair(result, "details.media.fullpath", currentAudio.getFullPath());
+               }
+
+               return result.toString();
+       }
+
+       /**
+        * Make the range description text
+        * @param inStats stats object
+        * @param inMaxSpeed maximum speed info
+        * @return string describing range
+        */
+       private static String makeRangeDescription(RangeStatsWithGradients inStats, SpeedValue inMaxSpeed)
+       {
+               StringBuffer result = new StringBuffer();
+               addTextPair(result, "details.track.points", "" + inStats.getNumPoints());
+               addTextPair(result, "details.range.numsegments", "" + inStats.getNumSegments());
+               final boolean hasMultipleSegments = (inStats.getNumSegments() > 1);
+
+               UnitSet unitSet = Config.getUnitSet();
+               final String speedUnitsStr = I18nManager.getText(unitSet.getSpeedUnit().getShortnameKey());
+               if (inMaxSpeed.isValid())
+               {
+                       final String maxSpeedStr = DisplayUtils.roundedNumber(inMaxSpeed.getValue()) + " " + speedUnitsStr;
+                       addTextPair(result, "details.range.maxspeed", maxSpeedStr);
+               }
+
+               addHeading(result, "dialog.fullrangedetails.colsegments");
+               final Unit distUnit = Config.getUnitSet().getDistanceUnit();
+               final String distUnitsStr = I18nManager.getText(distUnit.getShortnameKey());
+               final double movingDist = inStats.getMovingDistance();
+               addTextPair(result, "fieldname.distance", DisplayUtils.roundedNumber(movingDist),
+                       distUnitsStr);
+               long numSecs = inStats.getMovingDurationInSeconds();
+               addTextPair(result, "fieldname.duration", DisplayUtils.buildDurationString(numSecs));
+
+               if (numSecs > 0 && movingDist > 0.0)
+               {
+                       addTextPair(result, "details.range.avespeed", DisplayUtils.roundedNumber(movingDist/numSecs*3600.0),
+                               speedUnitsStr);
+                       addTextPair(result, "details.range.pace", DisplayUtils.buildDurationString((long) (numSecs/movingDist)),
+                               "/ " + distUnitsStr);
+               }
+               final Unit altUnit = unitSet.getAltitudeUnit();
+               final String altUnitsStr = I18nManager.getText(altUnit.getShortnameKey());
+               if (inStats.getMovingAltitudeRange().hasRange())
+               {
+                       AltitudeRange altRange = inStats.getMovingAltitudeRange();
+                       addTextPair(result, "fieldname.altitude", "" + altRange.getMinimum(altUnit) + altUnitsStr + " "
+                               + I18nManager.getText("details.altitude.to") + " "
+                               + altRange.getMaximum(altUnit) + altUnitsStr);
+                       addTextPair(result, "details.range.climb", "" + altRange.getClimb(altUnit), altUnitsStr);
+                       addTextPair(result, "details.range.descent", "" + altRange.getDescent(altUnit), altUnitsStr);
+                       addTextPair(result, "details.range.gradient", DisplayUtils.formatOneDp(inStats.getMovingGradient()), "%");
+                       if (numSecs > 0)
+                       {
+                               final String vertSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
+                               final String vertSpeedStr = DisplayUtils.roundedNumber(inStats.getMovingVerticalSpeed());
+                               addTextPair(result, "fieldname.verticalspeed", vertSpeedStr, vertSpeedUnitsStr);
+                       }
+               }
+
+               if (hasMultipleSegments)
+               {
+                       addHeading(result, "dialog.fullrangedetails.coltotal");
+                       final double totalDist = inStats.getTotalDistance();
+                       addTextPair(result, "fieldname.distance", DisplayUtils.roundedNumber(totalDist), distUnitsStr);
+                       long totalSecs = inStats.getTotalDurationInSeconds();
+                       addTextPair(result, "fieldname.duration", DisplayUtils.buildDurationString(totalSecs));
+                       if (totalSecs > 0 && totalDist > 0.0)
+                       {
+                               addTextPair(result, "details.range.avespeed", DisplayUtils.roundedNumber(totalDist/totalSecs*3600.0),
+                                       speedUnitsStr);
+                               addTextPair(result, "details.range.pace", DisplayUtils.buildDurationString((long) (totalSecs/totalDist)),
+                                       "/ " + distUnitsStr);
+                       }
+                       if (inStats.getTotalAltitudeRange().hasRange())
+                       {
+                               AltitudeRange altRange = inStats.getTotalAltitudeRange();
+                               addTextPair(result, "details.range.climb", "" + altRange.getClimb(altUnit), altUnitsStr);
+                               addTextPair(result, "details.range.descent", "" + altRange.getDescent(altUnit), altUnitsStr);
+                               addTextPair(result, "details.range.gradient", DisplayUtils.formatOneDp(inStats.getTotalGradient()), "%");
+                               if (totalSecs > 0)
+                               {
+                                       final String vertSpeedUnitsStr = I18nManager.getText(unitSet.getVerticalSpeedUnit().getShortnameKey());
+                                       final String vertSpeedStr = DisplayUtils.roundedNumber(inStats.getTotalVerticalSpeed());
+                                       addTextPair(result, "fieldname.verticalspeed", vertSpeedStr, vertSpeedUnitsStr);
+                               }
+                       }
+               }
+               return result.toString();
+       }
+
+       /**
+        * Add the label and value to the buffer
+        * @param inBuffer buffer to append to
+        * @param inLabelKey label key
+        * @param inValue value text
+        */
+       private static void addTextPair(StringBuffer inBuffer, String inLabelKey, String inValue)
+       {
+               addTextPair(inBuffer, inLabelKey, inValue, null);
+       }
+
+       /**
+        * Add the label and value to the buffer
+        * @param inBuffer buffer to append to
+        * @param inLabelKey label key
+        * @param inValue value text
+        * @param inUnits optional units string
+        */
+       private static void addTextPair(StringBuffer inBuffer, String inLabelKey, String inValue, String inUnits)
+       {
+               if (inValue != null && !inValue.equals(""))
+               {
+                       inBuffer.append(I18nManager.getText(inLabelKey));
+                       inBuffer.append(": ");
+                       inBuffer.append(inValue);
+                       if (inUnits != null && !inUnits.equals(""))
+                       {
+                               inBuffer.append(' ');
+                               inBuffer.append(inUnits);
+                       }
+                       inBuffer.append("\n");
+               }
+       }
+
+       /**
+        * Add a heading to the buffer
+        * @param inBuffer buffer to append to
+        * @param inLabelKey key for heading
+        */
+       private static void addHeading(StringBuffer inBuffer, String inLabelKey)
+       {
+               final String heading = I18nManager.getText(inLabelKey);
+               inBuffer.append('\n').append(heading).append('\n');
+               for (int i=0; i<heading.length(); i++)
+               {
+                       inBuffer.append('=');
+               }
+               inBuffer.append('\n');
+       }
+}
index 8e89360d7f79ba820f2743a001e319a42dd99a6c..1c127b3e109dd090a8f3bbff585766a20df8e17d 100644 (file)
@@ -147,7 +147,7 @@ public class CompressTrackFunction extends MarkAndDeleteFunction
                        public void actionPerformed(ActionEvent arg0)
                        {
                                preview();
-                       };
+                       }
                };
                // construct track details to be used by all algorithms
                TrackDetails details = new TrackDetails(_track);
index fbf0ccff49c550b79969cda539ec4b4bde008513..a949c1c16b8792c0a3e09890fdfe3c45561b66cb 100644 (file)
@@ -37,9 +37,9 @@ public abstract class SingleParameterAlgorithm extends CompressionAlgorithm
                _parameterField = new JTextField();
                // Add listener to parameter field to re-run preview (and en/disable ok) when param changed
                _parameterField.addKeyListener(new KeyListener() {
-                       public void keyTyped(java.awt.event.KeyEvent arg0) {};
-                       public void keyPressed(java.awt.event.KeyEvent arg0) {};
-                       public void keyReleased(java.awt.event.KeyEvent arg0) {if (isActivated()) _listener.actionPerformed(null);};
+                       public void keyTyped(java.awt.event.KeyEvent arg0) {}
+                       public void keyPressed(java.awt.event.KeyEvent arg0) {}
+                       public void keyReleased(java.awt.event.KeyEvent arg0) {if (isActivated()) _listener.actionPerformed(null);}
                });
        }
 
index 5fe6e7e7d9e307d901ad25c23b2a4ea021acc3d3..e0b7aed7354938616982ba8d933f6e531f37d651 100644 (file)
@@ -114,12 +114,7 @@ public class DistanceFunction extends GenericFunction
                // second table for distances
                _distModel = new DistanceTableModel();
                JTable distTable = new JTable(_distModel);
-               // Use reflection to call distTable.setAutoCreateRowSorter(true) which is new with Java 1.6
-               try {
-                       Class<?> d = Class.forName("javax.swing.JTable");
-                       d.getDeclaredMethod("setAutoCreateRowSorter", new Class[]{Boolean.TYPE}).invoke(distTable, Boolean.TRUE);
-               }
-               catch (Exception e) {}
+               distTable.setAutoCreateRowSorter(true);
                scrollPane = new JScrollPane(distTable);
                scrollPane.setPreferredSize(new Dimension(200, 250));
                mainPanel.add(scrollPane);
index 2c1d83d1337b56af1a8ab1fbad0a3cc938b3d5a2..b0340b9e5948f6735a2684488fc2ccea7dfb77c3 100644 (file)
@@ -37,7 +37,7 @@ public class DistanceTableModel extends GenericTableModel
        {
                if (inColumnIndex == 0) {return getPointName(inRowIndex);}
                if (_distances == null) {return 0.0;}
-               return new Double(_distances[inRowIndex]);
+               return Double.valueOf(_distances[inRowIndex]);
        }
 
        /**
index ef6979358edaefb64828469f18a2c41e6daf8846..8caad11892eb83dc219cd2a92bc0198dedb9456b 100644 (file)
@@ -23,7 +23,7 @@ import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
-import tim.prune.data.RangeStats;
+import tim.prune.data.RangeStatsWithGradients;
 import tim.prune.data.Selection;
 import tim.prune.data.Unit;
 import tim.prune.gui.DecimalNumberField;
@@ -54,7 +54,7 @@ public class EstimateTime extends GenericFunction
        private JLabel _descentParamLabel = null;
        private DecimalNumberField _gentleDescentField = null, _steepDescentField = null;
        /** Range stats */
-       private RangeStats _stats = null;
+       private RangeStatsWithGradients _stats = null;
 
 
        /**
@@ -78,7 +78,8 @@ public class EstimateTime extends GenericFunction
        {
                // Get the stats on the selection before launching the dialog
                Selection selection = _app.getTrackInfo().getSelection();
-               _stats = new RangeStats(_app.getTrackInfo().getTrack(), selection.getStart(), selection.getEnd());
+               _stats = new RangeStatsWithGradients(_app.getTrackInfo().getTrack(),
+                       selection.getStart(), selection.getEnd());
 
                if (_stats.getMovingDistance() < 0.01)
                {
@@ -297,7 +298,11 @@ public class EstimateTime extends GenericFunction
                EstimationParameters estParams = new EstimationParameters(Config.getConfigString(Config.KEY_ESTIMATION_PARAMS));
 
                String[] paramValues = estParams.getStrings();
-               if (paramValues == null || paramValues.length != 5) {return;} // TODO: What to do in case of error?
+               if (paramValues == null || paramValues.length != 5)
+               {
+                       // TODO: What to do in case of error?
+                       return;
+               }
                // Flat time is either for 5 km, 3 miles or 3 nm
                _flatSpeedLabel.setText(I18nManager.getText("dialog.estimatetime.parameters.timefor") +
                        " " + EstimationParameters.getStandardDistance() + ": ");
index 1b8046740b767b21bc8e55cdda4134831d092efa..afe59383da9c6d21962dcdfc0ae0b47748f83753 100644 (file)
@@ -6,7 +6,7 @@ import java.util.Locale;
 
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
-import tim.prune.data.RangeStats;
+import tim.prune.data.RangeStatsWithGradients;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
 
@@ -254,9 +254,11 @@ public class EstimationParameters
         * @param inStats stats of current range
         * @return estimated number of minutes required
         */
-       public double applyToStats(RangeStats inStats)
+       public double applyToStats(RangeStatsWithGradients inStats)
        {
-               if (inStats == null || !inStats.isValid()) return 0.0;
+               if (inStats == null) {
+                       return 0.0;
+               }
                final Unit METRES = UnitSetLibrary.UNITS_METRES;
                final double STANDARD_CLIMB = 100.0; // metres
                final double STANDARD_DISTANCE = 5.0; // kilometres
index 74021dd3b2ba46fd03e6fddbd9054b50ba6ae65a..3efaa2f33161808ac9bab161d4a9cb024dd9a8fa 100644 (file)
@@ -26,7 +26,7 @@ import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.data.DataPoint;
 import tim.prune.data.Distance;
-import tim.prune.data.RangeStats;
+import tim.prune.data.RangeStatsWithGradients;
 import tim.prune.data.Track;
 import tim.prune.data.Unit;
 import tim.prune.data.UnitSetLibrary;
@@ -100,7 +100,7 @@ public class LearnParameters extends GenericFunction implements Runnable
        {
                _progress.setMaximum(100);
                // Go through the track and collect the range stats for each sample
-               ArrayList<RangeStats> statsList = new ArrayList<RangeStats>(20);
+               ArrayList<RangeStatsWithGradients> statsList = new ArrayList<RangeStatsWithGradients>(20);
                Track track = _app.getTrackInfo().getTrack();
                final int numPoints = track.getNumPoints();
                final int sampleSize = numPoints / 30;
@@ -108,15 +108,15 @@ public class LearnParameters extends GenericFunction implements Runnable
                for (int i=0; i<30; i++)
                {
                        int startIndex = i * sampleSize;
-                       RangeStats stats = getRangeStats(track, startIndex, startIndex + sampleSize, prevStartIndex);
+                       RangeStatsWithGradients stats = getRangeStats(track, startIndex, startIndex + sampleSize, prevStartIndex);
                        if (stats != null && stats.getMovingDistanceKilometres() > 1.0
                                && !stats.getTimestampsIncomplete() && !stats.getTimestampsOutOfSequence()
                                && stats.getTotalDurationInSeconds() > 100
-                               && stats.getStartIndex() > prevStartIndex)
+                               && startIndex > prevStartIndex)
                        {
                                // System.out.println("Got stats for " + stats.getStartIndex() + " to " + stats.getEndIndex());
                                statsList.add(stats);
-                               prevStartIndex = stats.getStartIndex();
+                               prevStartIndex = startIndex;
                        }
                        _progress.setValue(i);
                }
@@ -246,7 +246,8 @@ public class LearnParameters extends GenericFunction implements Runnable
         * @param inPreviousStartIndex the previously used start index, or -1
         * @return range stats object or null if required information missing from this bit of the track
         */
-       private RangeStats getRangeStats(Track inTrack, int inStartIndex, int inEndIndex, int inPreviousStartIndex)
+       private RangeStatsWithGradients getRangeStats(Track inTrack, int inStartIndex,
+               int inEndIndex, int inPreviousStartIndex)
        {
                // Check parameters
                if (inTrack == null || inStartIndex < 0 || inEndIndex <= inStartIndex || inStartIndex > inTrack.getNumPoints()) {
@@ -297,7 +298,7 @@ public class LearnParameters extends GenericFunction implements Runnable
 
                // Check moving distance
                if (movingRads >= minimumRads) {
-                       return new RangeStats(inTrack, start, endIndex);
+                       return new RangeStatsWithGradients(inTrack, start, endIndex);
                }
                return null;
        }
@@ -307,12 +308,12 @@ public class LearnParameters extends GenericFunction implements Runnable
         * @param inStatsList list of (non-null) RangeStats objects
         * @return A matrix with n rows and 5 columns
         */
-       private static Matrix buildAMatrix(ArrayList<RangeStats> inStatsList)
+       private static Matrix buildAMatrix(ArrayList<RangeStatsWithGradients> inStatsList)
        {
                final Unit METRES = UnitSetLibrary.UNITS_METRES;
                Matrix result = new Matrix(inStatsList.size(), 5);
                int row = 0;
-               for (RangeStats stats : inStatsList)
+               for (RangeStatsWithGradients stats : inStatsList)
                {
                        result.setValue(row, 0, stats.getMovingDistanceKilometres());
                        result.setValue(row, 1, stats.getGentleAltitudeRange().getClimb(METRES));
@@ -329,11 +330,11 @@ public class LearnParameters extends GenericFunction implements Runnable
         * @param inStatsList list of (non-null) RangeStats objects
         * @return B matrix with single column of n rows
         */
-       private static Matrix buildBMatrix(ArrayList<RangeStats> inStatsList)
+       private static Matrix buildBMatrix(ArrayList<RangeStatsWithGradients> inStatsList)
        {
                Matrix result = new Matrix(inStatsList.size(), 1);
                int row = 0;
-               for (RangeStats stats : inStatsList)
+               for (RangeStatsWithGradients stats : inStatsList)
                {
                        result.setValue(row, 0, stats.getMovingDurationInSeconds() / 60.0); // convert seconds to minutes
                        row++;
@@ -372,12 +373,13 @@ public class LearnParameters extends GenericFunction implements Runnable
         * @param inRowToIgnore row index to ignore, or -1 to use them all
         * @return true if the samples look ok
         */
-       private static boolean isRangeSetSufficient(ArrayList<RangeStats> inRangeSet, int inRowToIgnore)
+       private static boolean isRangeSetSufficient(ArrayList<RangeStatsWithGradients> inRangeSet, int inRowToIgnore)
        {
-               int numGC = 0, numSC = 0, numGD = 0, numSD = 0; // number of samples with gentle/steep climb/descent values > 0
+               // number of samples with gentle/steep climb/descent values > 0
+               int numGC = 0, numSC = 0, numGD = 0, numSD = 0;
                final Unit METRES = UnitSetLibrary.UNITS_METRES;
                int i = 0;
-               for (RangeStats stats : inRangeSet)
+               for (RangeStatsWithGradients stats : inRangeSet)
                {
                        if (i != inRowToIgnore)
                        {
@@ -396,7 +398,7 @@ public class LearnParameters extends GenericFunction implements Runnable
         * @param inStatsList list of stats
         * @return results in an object
         */
-       private MatrixResults reduceSamples(ArrayList<RangeStats> inStatsList)
+       private MatrixResults reduceSamples(ArrayList<RangeStatsWithGradients> inStatsList)
        {
                int statsIndexToRemove = -1;
                Matrix answer = null;
diff --git a/src/tim/prune/function/gpsies/FormPoster.java b/src/tim/prune/function/gpsies/FormPoster.java
deleted file mode 100644 (file)
index 5939e63..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-package tim.prune.function.gpsies;\r
-\r
-import java.net.HttpURLConnection;\r
-import java.net.URLConnection;\r
-import java.net.URL;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.util.Random;\r
-import java.io.OutputStream;\r
-\r
-/**\r
- * Taken from the Client HTTP Request class at com.myjavatools.web\r
- * and subsequently simplified and modified\r
- * @author Vlad Patryshev\r
- */\r
-public class FormPoster\r
-{\r
-       private URLConnection _connection = null;\r
-       private OutputStream _os = null;\r
-       private static final Random RANDOM_GEN = new Random();\r
-       private static final String BOUNDARY = "---------------------------"\r
-               + randomString() + randomString() + randomString();\r
-\r
-\r
-       /** Connect (if not already connected) */\r
-       protected void connect() throws IOException {\r
-               if (_os == null) _os = _connection.getOutputStream();\r
-       }\r
-\r
-       /** Write a single character */\r
-       protected void write(char c) throws IOException {\r
-               connect();\r
-               _os.write(c);\r
-       }\r
-\r
-       /** Write a string */\r
-       protected void write(String s) throws IOException {\r
-               connect();\r
-               _os.write(s.getBytes());\r
-       }\r
-\r
-       /** Write a -r-n newline sequence */\r
-       protected void newline() throws IOException {\r
-               write("\r\n");\r
-       }\r
-\r
-       /** Write a string followed by a newline */\r
-       protected void writeln(String s) throws IOException {\r
-               write(s);\r
-               newline();\r
-       }\r
-\r
-       /** Generate a random alphanumeric string */\r
-       private static String randomString() {\r
-               return Long.toString(RANDOM_GEN.nextLong(), 36);\r
-       }\r
-\r
-       /** Write a boundary marker */\r
-       private void boundary() throws IOException {\r
-               write("--");\r
-               write(BOUNDARY);\r
-       }\r
-\r
-\r
-       /**\r
-        * Creates a new multipart POST HTTP request for a specified URL\r
-        * @param url the URL to send request to\r
-        * @throws IOException\r
-        */\r
-       public FormPoster(URL inUrl) throws IOException\r
-       {\r
-               _connection = inUrl.openConnection();\r
-               _connection.setDoOutput(true);\r
-               _connection.setRequestProperty("Content-Type",\r
-                       "multipart/form-data; boundary=" + BOUNDARY);\r
-       }\r
-\r
-       /** Write a header with the given name */\r
-       private void writeName(String inName) throws IOException\r
-       {\r
-               newline();\r
-               write("Content-Disposition: form-data; name=\"");\r
-               write(inName);\r
-               write('"');\r
-       }\r
-\r
-       /**\r
-        * adds a string parameter to the request\r
-        * @param name parameter name\r
-        * @param value parameter value\r
-        * @throws IOException\r
-        */\r
-       public void setParameter(String inName, String inValue) throws IOException\r
-       {\r
-               boundary();\r
-               writeName(inName);\r
-               newline(); newline();\r
-               writeln(inValue);\r
-       }\r
-\r
-       /** Pipe the contents of the input stream to the output stream */\r
-       private static void pipe(InputStream in, OutputStream out) throws IOException\r
-       {\r
-               byte[] buf = new byte[500000];\r
-               int nread;\r
-               synchronized (in) {\r
-                       while((nread = in.read(buf, 0, buf.length)) >= 0) {\r
-                               out.write(buf, 0, nread);\r
-                       }\r
-               }\r
-               out.flush();\r
-               buf = null;\r
-       }\r
-\r
-       /**\r
-        * adds a file parameter to the request\r
-        * @param inName parameter name\r
-        * @param inFilename the name of the file\r
-        * @param inStream input stream to read the contents of the file from\r
-        * @throws IOException\r
-        */\r
-       public void setParameter(String inName, String inFilename, InputStream inStream) throws IOException\r
-       {\r
-               boundary();\r
-               writeName(inName);\r
-               write("; filename=\"");\r
-               write(inFilename);\r
-               write('"');\r
-               newline();\r
-               write("Content-Type: ");\r
-               String type = URLConnection.guessContentTypeFromName(inFilename);\r
-               if (type == null) {type = "application/octet-stream";}\r
-               writeln(type);\r
-               newline();\r
-               pipe(inStream, _os);\r
-               newline();\r
-       }\r
-\r
-       /**\r
-        * posts the requests to the server\r
-        * @return input stream with the server response\r
-        * @throws IOException\r
-        */\r
-       public InputStream post() throws IOException\r
-       {\r
-               boundary();\r
-               writeln("--");\r
-               _os.close();\r
-               return _connection.getInputStream();\r
-       }\r
-\r
-       /**\r
-        * @return the HTTP response code, 200 for success or -1 if not available\r
-        */\r
-       public int getResponseCode() throws IOException\r
-       {\r
-               if (_connection != null && _connection instanceof HttpURLConnection) {\r
-                       return ((HttpURLConnection) _connection).getResponseCode();\r
-               }\r
-               return -1;\r
-       }\r
-}\r
diff --git a/src/tim/prune/function/gpsies/GetGpsiesFunction.java b/src/tim/prune/function/gpsies/GetGpsiesFunction.java
deleted file mode 100644 (file)
index cdcc9a9..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-package tim.prune.function.gpsies;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.ArrayList;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import tim.prune.App;
-import tim.prune.GpsPrune;
-import tim.prune.I18nManager;
-import tim.prune.function.search.GenericDownloaderFunction;
-import tim.prune.function.search.SearchResult;
-import tim.prune.load.xml.XmlFileLoader;
-import tim.prune.load.xml.ZipFileLoader;
-
-/**
- * Function to load track information from Gpsies.com
- * according to the currently viewed area
- */
-public class GetGpsiesFunction extends GenericDownloaderFunction
-{
-       /** Number of results per page */
-       private static final int RESULTS_PER_PAGE = 20;
-       /** Maximum number of results to get */
-       private static final int MAX_RESULTS = 60;
-       /** New API key (specific to this program) */
-       private static final String GPSIES_API_KEY = "oumgvvbckiwpvsnb";
-
-
-       /**
-        * Constructor
-        * @param inApp App object
-        */
-       public GetGpsiesFunction(App inApp) {
-               super(inApp);
-       }
-
-       /**
-        * @return name key
-        */
-       public String getNameKey() {
-               return "function.getgpsies";
-       }
-
-       /**
-        * @param inColNum index of column, 0 or 1
-        * @return key for this column
-        */
-       protected String getColumnKey(int inColNum)
-       {
-               if (inColNum == 0) return "dialog.gpsies.column.name";
-               return "dialog.gpsies.column.length";
-       }
-
-
-       /**
-        * Run method to call gpsies.com in separate thread
-        */
-       public void run()
-       {
-               _statusLabel.setText(I18nManager.getText("confirm.running"));
-               // Act on callback to update list and send another request if necessary
-               double[] coords = _app.getViewport().getBounds();
-               int currPage = 1;
-
-               ArrayList<SearchResult> trackList = null;
-               URL url = null;
-               String descMessage = "";
-               InputStream inStream = null;
-               // Loop for each page of the results
-               do
-               {
-                       // Example http://ws.gpsies.com/api.do?BBOX=10,51,12,53&limit=20&resultPage=1&key=oumgvvbckiwpvsnb
-                       String urlString = "http://ws.gpsies.com/api.do?BBOX=" +
-                               coords[1] + "," + coords[0] + "," + coords[3] + "," + coords[2] +
-                               "&limit=" + RESULTS_PER_PAGE + "&resultPage=" + currPage +
-                               "&key=" + GPSIES_API_KEY;
-                       // Parse the returned XML with a special handler
-                       GpsiesXmlHandler xmlHandler = new GpsiesXmlHandler();
-                       try
-                       {
-                               url = new URL(urlString);
-                               SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
-                               URLConnection conn = url.openConnection();
-                               conn.setRequestProperty("User-Agent", "GpsPrune v" + GpsPrune.VERSION_NUMBER);
-                               inStream = conn.getInputStream();
-                               saxParser.parse(inStream, xmlHandler);
-                       }
-                       catch (Exception e) {
-                               descMessage = e.getClass().getName() + " - " + e.getMessage();
-                       }
-                       // Close stream and ignore errors
-                       try {
-                               inStream.close();
-                       } catch (Exception e) {}
-                       // Add track list to model
-                       trackList = xmlHandler.getTrackList();
-                       _trackListModel.addTracks(trackList);
-
-                       // Compare number of results with results per page and call again if necessary
-                       currPage++;
-               }
-               while (trackList != null && trackList.size() == RESULTS_PER_PAGE
-                       && _trackListModel.getRowCount() < MAX_RESULTS && !_cancelled);
-               // Set status label according to error or "none found", leave blank if ok
-               if (descMessage.equals("") && (trackList == null || trackList.size() == 0)) {
-                       descMessage = I18nManager.getText("dialog.gpsies.nonefound");
-               }
-               _statusLabel.setText(descMessage);
-       }
-
-       /**
-        * Load the selected track(s)
-        */
-       protected void loadSelected()
-       {
-               // Find the row(s) selected in the table and get the corresponding track
-               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 url = _trackListModel.getTrack(rowNum).getDownloadLink();
-                               XmlFileLoader xmlLoader = new XmlFileLoader(_app);
-                               ZipFileLoader loader = new ZipFileLoader(_app, xmlLoader);
-                               if (i>0) _app.autoAppendNextFile();
-                               try
-                               {
-                                       loader.openStream(new URL(url).openStream());
-                               }
-                               catch (IOException ioe) {
-                                       System.err.println("IO Exception : " + ioe.getMessage());
-                               }
-                       }
-               }
-               // Close the dialog
-               _cancelled = true;
-               _dialog.dispose();
-       }
-}
diff --git a/src/tim/prune/function/gpsies/GpsiesXmlHandler.java b/src/tim/prune/function/gpsies/GpsiesXmlHandler.java
deleted file mode 100644 (file)
index c549c59..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package tim.prune.function.gpsies;
-
-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 gpsies.com
- */
-public class GpsiesXmlHandler extends DefaultHandler
-{
-       private String _value = null;
-       private ArrayList<SearchResult> _trackList = null;
-       private SearchResult _track = 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("tracks")) {
-                       _trackList = new ArrayList<SearchResult>();
-               }
-               else if (inTagName.equals("track")) {
-                       _track = new SearchResult();
-               }
-               _value = null;
-               super.startElement(inUri, inLocalName, inTagName, inAttributes);
-       }
-
-       /**
-        * React to the end of an XML tag
-        */
-       public void endElement(String inUri, String inLocalName, String inTagName)
-       throws SAXException
-       {
-               if (inTagName.equals("track")) {
-                       _trackList.add(_track);
-               }
-               else if (inTagName.equals("title")) {
-                       _track.setTrackName(_value);
-               }
-               else if (inTagName.equals("description")) {
-                       _track.setDescription(_value);
-               }
-               else if (inTagName.equals("fileId")) {
-                       _track.setWebUrl("https://gpsies.com/map.do?fileId=" + _value);
-               }
-               else if (inTagName.equals("trackLengthM")) {
-                       try {
-                               _track.setLength(Double.parseDouble(_value));
-                       }
-                       catch (NumberFormatException nfe) {}
-               }
-               else if (inTagName.equals("downloadLink")) {
-                       _track.setDownloadLink(_value);
-               }
-               super.endElement(inUri, inLocalName, inTagName);
-       }
-
-       /**
-        * React to characters received inside tags
-        */
-       public void characters(char[] inCh, int inStart, int inLength)
-       throws SAXException
-       {
-               String value = new String(inCh, inStart, inLength);
-               _value = (_value==null?value:_value+value);
-               super.characters(inCh, inStart, inLength);
-       }
-
-       /**
-        * @return the list of tracks
-        */
-       public ArrayList<SearchResult> getTrackList()
-       {
-               return _trackList;
-       }
-}
diff --git a/src/tim/prune/function/gpsies/UploadGpsiesFunction.java b/src/tim/prune/function/gpsies/UploadGpsiesFunction.java
deleted file mode 100644 (file)
index 3cef9db..0000000
+++ /dev/null
@@ -1,332 +0,0 @@
-package tim.prune.function.gpsies;
-
-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.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPasswordField;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.border.EtchedBorder;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
-import tim.prune.App;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
-import tim.prune.function.browser.BrowserLauncher;
-import tim.prune.gui.GuiGridLayout;
-import tim.prune.save.GpxExporter;
-import tim.prune.save.SettingsForExport;
-
-/**
- * Function to upload track information up to Gpsies.com
- */
-public class UploadGpsiesFunction extends GenericFunction
-{
-       /** Dialog object */
-       private JDialog _dialog = null;
-       /** Edit box for user name */
-       private JTextField _usernameField = null;
-       /** Edit box for password */
-       private JPasswordField _passwordField = null;
-       /** Name of track */
-       private JTextField _nameField = null;
-       /** Description */
-       private JTextArea _descField = null;
-       /** Private checkbox */
-       private JCheckBox _privateCheckbox = null;
-       /** Activity checkboxes */
-       private JCheckBox[] _activityCheckboxes = null;
-       /** Writer object for GPX export */
-       private OutputStreamWriter _writer = null;
-       /** upload button */
-       private JButton _uploadButton = null;
-
-       /** URL to post form to */
-       private static final String GPSIES_URL = "http://www.gpsies.com/upload.do";
-       /** Keys for describing activities */
-       private static final String[] ACTIVITY_KEYS = {"trekking", "walking", "jogging",
-               "biking", "motorbiking", "snowshoe", "sailing", "skating"};
-
-       /**
-        * Constructor
-        * @param inApp App object
-        */
-       public UploadGpsiesFunction(App inApp) {
-               super(inApp);
-       }
-
-       /**
-        * @return name key
-        */
-       public String getNameKey() {
-               return "function.uploadgpsies";
-       }
-
-       /**
-        * Begin the function
-        */
-       public void begin()
-       {
-               // Initialise dialog, show empty list
-               if (_dialog == null)
-               {
-                       _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
-                       _dialog.setLocationRelativeTo(_parentFrame);
-                       _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-                       _dialog.getContentPane().add(makeDialogComponents());
-                       _dialog.pack();
-               }
-               // Show dialog
-               _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());
-
-               JPanel gridPanel = new JPanel();
-               GuiGridLayout grid = new GuiGridLayout(gridPanel);
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.username")));
-               _usernameField = new JTextField(15);
-               grid.add(_usernameField);
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.password")));
-               _passwordField = new JPasswordField(15);
-               grid.add(_passwordField);
-               // Track name and description
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.column.name")));
-               _nameField = new JTextField(15);
-               grid.add(_nameField);
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.description")));
-               _descField = new JTextArea(5, 15);
-               _descField.setLineWrap(true);
-               _descField.setWrapStyleWord(true);
-               grid.add(new JScrollPane(_descField));
-               // Listener on all these text fields to enable/disable the ok button
-               KeyAdapter keyListener = new KeyAdapter() {
-                       /** Key released */
-                       public void keyReleased(KeyEvent inE) {
-                               enableOK();
-                               if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
-                                       _dialog.dispose();
-                               }
-                       }
-               };
-               _usernameField.addKeyListener(keyListener);
-               _passwordField.addKeyListener(keyListener);
-               _nameField.addKeyListener(keyListener);
-               // Listen for tabs on description field, to change focus not enter tabs
-               _descField.addKeyListener(new KeyAdapter() {
-                       /** Key pressed */
-                       public void keyPressed(KeyEvent inE) {
-                               if (inE.getKeyCode() == KeyEvent.VK_TAB) {
-                                       inE.consume();
-                                       if (inE.isShiftDown()) {
-                                               _nameField.requestFocusInWindow();
-                                       }
-                                       else {
-                                               _privateCheckbox.requestFocusInWindow();
-                                       }
-                               }
-                       }
-               });
-               // Listen for Ctrl-backspace on password field (delete contents)
-               _passwordField.addKeyListener(new KeyAdapter() {
-                       /** Key released */
-                       public void keyReleased(KeyEvent inE) {
-                               if (inE.isControlDown() && (inE.getKeyCode() == KeyEvent.VK_BACK_SPACE
-                                       || inE.getKeyCode() == KeyEvent.VK_DELETE)) {
-                                       _passwordField.setText("");
-                               }
-                       }
-               });
-               // Checkbox for private / public
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.keepprivate")));
-               _privateCheckbox = new JCheckBox();
-               _privateCheckbox.setSelected(true);
-               grid.add(_privateCheckbox);
-
-               // panel for activity type checkboxes
-               JPanel activityPanel = new JPanel();
-               activityPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
-               ChangeListener checkListener = new ChangeListener() {
-                       public void stateChanged(ChangeEvent arg0) {
-                               enableOK();
-                       }
-               };
-               // Why not a simple grid layout here?
-               GuiGridLayout actGrid = new GuiGridLayout(activityPanel, new double[] {1.0, 1.0}, new boolean[] {false, false});
-               final int numActivities = ACTIVITY_KEYS.length;
-               _activityCheckboxes = new JCheckBox[numActivities];
-               for (int i=0; i<numActivities; i++)
-               {
-                       _activityCheckboxes[i] = new JCheckBox(I18nManager.getText("dialog.gpsies.activity." + ACTIVITY_KEYS[i]));
-                       _activityCheckboxes[i].addChangeListener(checkListener);
-                       actGrid.add(_activityCheckboxes[i]);
-               }
-               grid.add(new JLabel(I18nManager.getText("dialog.gpsies.activities")));
-               grid.add(activityPanel);
-               JPanel midPanel = new JPanel();
-               midPanel.setLayout(new BoxLayout(midPanel, BoxLayout.Y_AXIS));
-               midPanel.add(gridPanel);
-               dialogPanel.add(midPanel, BorderLayout.CENTER);
-
-               // button panel at bottom
-               JPanel buttonPanel = new JPanel();
-               buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
-               _uploadButton = new JButton(I18nManager.getText("button.upload"));
-               _uploadButton.setEnabled(false);
-               _uploadButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               startUpload();
-                       }
-               });
-               buttonPanel.add(_uploadButton);
-               JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
-               cancelButton.addActionListener(new ActionListener() {
-                       public void actionPerformed(ActionEvent e) {
-                               _dialog.dispose();
-                       }
-               });
-               buttonPanel.add(cancelButton);
-               dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
-               dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
-               return dialogPanel;
-       }
-
-
-       /**
-        * Check the inputs and enable or disable the upload button
-        */
-       private void enableOK()
-       {
-               // Check for lengths of input fields - only username, password and filename are required
-               boolean ok = (_usernameField.getText().length() > 0 && _nameField.getText().length() > 0);
-               if (ok) {
-                       // also check password field
-                       char[] pass = _passwordField.getPassword();
-                       ok = pass.length > 0;
-                       for (int i=0; i<pass.length; i++) {pass[i] = '0';} // recommended by javadoc
-                       if (ok) {
-                               ok = false;
-                               for (int i=0; i<_activityCheckboxes.length; i++) {
-                                       ok = ok || _activityCheckboxes[i].isSelected();
-                               }
-                       }
-               }
-               _uploadButton.setEnabled(ok);
-       }
-
-
-       /**
-        * Start the upload process (require separate thread?)
-        */
-       private void startUpload()
-       {
-               BufferedReader reader = null;
-               try
-               {
-                       FormPoster poster = new FormPoster(new URL(GPSIES_URL));
-                       poster.setParameter("device", "Prune");
-                       poster.setParameter("username", _usernameField.getText());
-                       poster.setParameter("password", new String(_passwordField.getPassword()));
-                       boolean hasActivity = false;
-                       for (int i=0; i<ACTIVITY_KEYS.length; i++)
-                       {
-                               if (_activityCheckboxes[i].isSelected()) {
-                                       hasActivity = true;
-                                       poster.setParameter("trackTypes", ACTIVITY_KEYS[i]);
-                               }
-                       }
-                       if (!hasActivity) {poster.setParameter("trackTypes", "walking");} // default if none given
-                       poster.setParameter("filename", _nameField.getText());
-                       poster.setParameter("fileDescription", _descField.getText());
-                       poster.setParameter("startpointCountry", "DE");
-                       poster.setParameter("endpointCountry", "DE"); // both those will be corrected by gpsies
-                       poster.setParameter("status", (_privateCheckbox.isSelected()?"3":"1"));
-                       poster.setParameter("submit", "speichern"); // required
-                       // Use Pipes to connect the GpxExporter's output with the FormPoster's input
-                       PipedInputStream iStream = new PipedInputStream();
-                       PipedOutputStream oStream = new PipedOutputStream(iStream);
-                       _writer = new OutputStreamWriter(oStream);
-                       new Thread(new Runnable() {
-                               public void run() {
-                                       try {
-                                               GpxExporter.exportData(_writer, _app.getTrackInfo(), _nameField.getText(),
-                                                       null, new SettingsForExport(), null);
-                                       } catch (IOException e) {}
-                                       finally {
-                                               try {_writer.close();} catch (IOException e) {}
-                                       }
-                               }
-                       }).start();
-                       poster.setParameter("formFile", "filename.gpx", iStream);
-
-                       BufferedInputStream answer = new BufferedInputStream(poster.post());
-                       int response = poster.getResponseCode();
-                       reader = new BufferedReader(new InputStreamReader(answer));
-                       String line = reader.readLine();
-                       // Try to extract gpsies page url from the returned message
-                       String pageUrl = null;
-                       if (response == 200 && line.substring(0, 2).toUpperCase().equals("OK"))
-                       {
-                               final int bracketPos = line.indexOf('[');
-                               if (bracketPos > 0 && line.endsWith("]")) {
-                                       pageUrl = line.substring(bracketPos + 1, line.length()-1);
-                               }
-                       }
-                       if (pageUrl != null)
-                       {
-                               // OK received and managed to extract a Url from the return message.
-                               int userChoice = JOptionPane.showConfirmDialog(_app.getFrame(),
-                                       I18nManager.getText("dialog.gpsies.confirmopenpage"),
-                                       I18nManager.getText(getNameKey()), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
-                               if (userChoice == JOptionPane.OK_OPTION) {
-                                       BrowserLauncher.launchBrowser(pageUrl);
-                               }
-                       }
-                       else {
-                               _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getText("error.gpsies.uploadnotok")
-                                       + ": " + line);
-                       }
-               }
-               catch (MalformedURLException e) {}
-               catch (IOException ioe) {
-                       _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getText("error.gpsies.uploadfailed") + ": "
-                               + ioe.getClass().getName() + " : " + ioe.getMessage());
-               }
-               finally {
-                       try {if (reader != null) reader.close();} catch (IOException e) {}
-               }
-               _dialog.dispose();
-       }
-}
diff --git a/src/tim/prune/function/olc/CoordPair.java b/src/tim/prune/function/olc/CoordPair.java
new file mode 100644 (file)
index 0000000..778b46d
--- /dev/null
@@ -0,0 +1,75 @@
+package tim.prune.function.olc;
+
+class ParseException extends Exception {}
+
+/**
+ * Pair of coordinates
+ */
+class CoordPair
+{
+       /** Alphabet of allowed characters */
+       private static final String ALPHABET = "23456789CFGHJMPQRVWX";
+
+       public double lat = 0.0;
+       public double lon = 0.0;
+
+       /** Constructor */
+       public CoordPair(double inLat, double inLon)
+       {
+               lat = inLat;
+               lon = inLon;
+       }
+
+       /** Constant pair to represent padding */
+       public static CoordPair PADDING = new CoordPair(-1.0, -1.0);
+
+       /**
+        * Try to parse the given pair of characters into a CoordPair
+        * @param inFirst first character of pair
+        * @param inSecond second character of pair
+        * @return CoordPair from (0, 0) to (19/20, 19/20)
+        * @throws ParseException
+        */
+       public static CoordPair decode(char inFirst, char inSecond) throws ParseException
+       {
+               final boolean isFirstPadding = (inFirst == '0');
+               final boolean isSecondPadding = (inSecond == '0');
+               if (isFirstPadding && isSecondPadding) {return CoordPair.PADDING;}
+               if (isFirstPadding || isSecondPadding) {throw new ParseException();}
+               // Try to turn these characters into numbers
+               final double lat = decodeChar(inFirst);
+               final double lon = decodeChar(inSecond);
+               return new CoordPair(lat / 20.0, lon / 20.0);
+       }
+
+       /**
+        * Try to parse the given single character into a CoordPair
+        * @param inChar single character from level 11
+        * @return CoordPair from (0, 0) to (19/20, 19/20)
+        * @throws ParseException
+        */
+       public static CoordPair decode(char inChar) throws ParseException
+       {
+               // Try to turn this character into a number
+               final int charIndex = decodeChar(inChar);
+               final int lat = charIndex / 4;
+               final int lon = charIndex % 4;
+               return new CoordPair(lat / 5.0, lon / 4.0);
+       }
+
+       /**
+        * Get the index from the given character
+        * @param inChar character from OLC
+        * @return index from 0 to 19
+        * @throws ParseException if character not found
+        */
+       private static int decodeChar(char inChar) throws ParseException
+       {
+               final int index = ALPHABET.indexOf(inChar);
+               if (index < 0)
+               {
+                       throw new ParseException();
+               }
+               return index;
+       }
+}
diff --git a/src/tim/prune/function/olc/OlcArea.java b/src/tim/prune/function/olc/OlcArea.java
new file mode 100644 (file)
index 0000000..bc7a3e8
--- /dev/null
@@ -0,0 +1,21 @@
+package tim.prune.function.olc;
+
+/**
+ * Class to represent the result of an OLC decoding
+ */
+public class OlcArea
+{
+       public double minLat = 0.0;
+       public double maxLat = 0.0;
+       public double minLon = 0.0;
+       public double maxLon = 0.0;
+
+       /** Constructor */
+       public OlcArea(double inMinLat, double inMinLon, double inMaxLat, double inMaxLon)
+       {
+               minLat = inMinLat;
+               minLon = inMinLon;
+               maxLat = inMaxLat;
+               maxLon = inMaxLon;
+       }
+}
diff --git a/src/tim/prune/function/olc/OlcDecoder.java b/src/tim/prune/function/olc/OlcDecoder.java
new file mode 100644 (file)
index 0000000..122f6fe
--- /dev/null
@@ -0,0 +1,101 @@
+package tim.prune.function.olc;
+
+
+/**
+ * Decoder of OLC (Open Location Code) strings
+ */
+public class OlcDecoder
+{
+       /**
+        * Decode the given String into an OlcArea object
+        * @param inCode code representing an OLC
+        * @return an OlcArea object, or null if parsing failed
+        */
+       public static OlcArea decode(String inCode)
+       {
+               if (inCode == null || inCode.length() < 8) {
+                       return null;
+               }
+               String code = inCode.trim().toUpperCase();
+               if (code.length() < 8 || code.length() > 12) {
+                       return null;
+               }
+               double lat = 0.0, lon = 0.0;
+               double resolution = 400.0;
+               int charPos = 0;
+               int numSteps = 0;
+               boolean amPadding = false;
+               try
+               {
+                       while (charPos < inCode.length())
+                       {
+                               if (charPos == 0 || charPos == 2 || charPos == 4 || charPos == 6 || charPos == 9)
+                               {
+                                       // take next two characters, make pair, position += 2
+                                       CoordPair pair = CoordPair.decode(code.charAt(charPos), code.charAt(charPos+1));
+                                       if (pair == CoordPair.PADDING) {
+                                               amPadding = true;
+                                       }
+                                       else if (amPadding)
+                                       {
+                                               return null;
+                                       }
+                                       else
+                                       {
+                                               // Add to current lat, lon
+                                               lat += (pair.lat * resolution);
+                                               lon += (pair.lon * resolution);
+                                               numSteps++;
+                                               resolution /= 20.0;
+                                       }
+                                       charPos += 2;
+                               }
+                               else if (charPos == 8)
+                               {
+                                       if (code.charAt(charPos) != '+')
+                                       {
+                                               return null;
+                                       }
+                                       charPos += 1;
+                               }
+                               else if (charPos == 11)
+                               {
+                                       // take next character, make pair
+                                       CoordPair pair = CoordPair.decode(code.charAt(charPos));
+                                       // Add to current lat, lon
+                                       lat += (pair.lat * resolution);
+                                       lon += (pair.lon * resolution);
+                                       charPos += 1;
+                                       numSteps++;
+                                       resolution /= 20.0;
+                               }
+                               else
+                               {
+                                       return null;
+                               }
+                       }
+
+                       // Make OlcArea object and return it
+                       if (numSteps < 1)
+                       {
+                               return null;
+                       }
+                       else if (numSteps < 6)
+                       {
+                               // make four points
+                               lat -= 90.0;
+                               lon -= 180.0;
+                               return new OlcArea(lat, lon, lat+resolution, lon+resolution);
+                       }
+                       else
+                       {
+                               // make single point:
+                               lat -= 90.0;
+                               lon -= 180.0;
+                               return new OlcArea(lat, lon, lat+resolution*2.5, lon+resolution*2.0);
+                       }
+               }
+               catch (ParseException e) {}
+               return null;
+       }
+}
\ No newline at end of file
index fa8d4c33d90bf43838f19f039d9b510226fccf51..079bf9d11e0ce70852fd21131aa5f4ced0dcd72a 100644 (file)
@@ -25,11 +25,10 @@ import tim.prune.App;
 import tim.prune.GenericFunction;
 import tim.prune.I18nManager;
 import tim.prune.function.browser.BrowserLauncher;
-import tim.prune.function.gpsies.TrackListModel;
 
 /**
  * Function to load track information from any source,
- * subclassed for special cases like gpsies, wikipedia or OSM
+ * subclassed for special cases like wikipedia or OSM
  */
 public abstract class GenericDownloaderFunction extends GenericFunction implements Runnable
 {
index d757b017a2d6456e9e43475418116c84e6144ec3..52b902f85e08f04b7d02f74ff239514427c34aaf 100644 (file)
@@ -1,7 +1,7 @@
 package tim.prune.function.search;
 
 /**
- * Class to hold a search result from wikipedia / gpsies etc
+ * Class to hold a search result from wikipedia or other online service
  */
 public class SearchResult implements Comparable<SearchResult>
 {
similarity index 95%
rename from src/tim/prune/function/gpsies/TrackListModel.java
rename to src/tim/prune/function/search/TrackListModel.java
index 850921303801d33878c6b429ec2709087ed7fd6a..d9163be25cb894c27ea7feadf47cad7909bbc223 100644 (file)
@@ -1,4 +1,4 @@
-package tim.prune.function.gpsies;
+package tim.prune.function.search;
 
 import java.text.NumberFormat;
 import java.util.ArrayList;
@@ -9,10 +9,9 @@ import javax.swing.table.AbstractTableModel;
 import tim.prune.I18nManager;
 import tim.prune.config.Config;
 import tim.prune.data.Unit;
-import tim.prune.function.search.SearchResult;
 
 /**
- * Model for list of tracks from a search result (eg gpsies.com, geonames, overpass)
+ * Model for list of tracks from a search result (eg geonames, overpass)
  */
 public class TrackListModel extends AbstractTableModel
 {
index 364723712a0c785fabb6de8d9c6643d55fabef40..6b1ba55192f6dbb68666bab73c72a3fc20121539 100644 (file)
+116/122/A-180 Z light pillbox          49 57 30.7 N    14 05 23.85 E
+A-4/51/A-200 Y light pillbox   Back to 1938 state restored Pillbox Type 37 built in a unique modification into road embankment between Dolní Bezděkov and Bratronice, Kladno District, Czech Republic.       50 04 47.94 N   14 02 01.85 E
 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      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.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 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 Montmajour           43 42 20.16 N   4 39 50.04 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
+Abbaye Saint Wandrille Monastery Saint Wandrille in France     49 31 46.42 N   0 45 59.58 E
+Abeilhan       Abeilhan is a commune of the Hérault département in the Region of Occitanie - France. 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
+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
 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
+Adissan        Adissan is a commune of the Hérault département in the Region of Occitanie - France.  43 32 09.96 N   3 25 45.12 E
+Aérodrome de Besançon-Thise          47 16 25.68 N   6 04 59.09 E
+A Estrada      A Estrada is a municipality in Galicia, Spain in the province of Pontevedra.    41 32 35.1 N    2 12 59.63 E
+Agel   Agel is a commune of the Hérault département - France.        43 20 18.96 N   2 51 13.68 E
 Agrón, Ames           42 54 20.32 N   8 41 43.21 W
+A Guarda       A Guarda is a municipality in Galicia, in the province of Pontevedra.   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
 Aguiño, Ribeira               42 31 24.99 N   9 00 57.6 W
-Aichi prefecture               35 04 59 N      136 58 59 E
 Aiguafreda     Montseny        41 46 05.2 N    2 15 05.39 E
+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
 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
+Alte Kanzlei, Stuttgart                48 46 39.1 N    9 10 42.97 E
 Alt Katholische Christuskirche Offenbach               50 06 01.91 N   8 45 52.28 E
+A Merca        A Merca is a municipality in Galicia, in the province of Ourense.       42 13 22.53 N   7 54 15.55 W
 Ames           42 51 35.95 N   8 38 58.21 W
+A Mezquita             42 00 42.5 N    7 02 43.17 W
 Amoeiro                42 24 37.45 N   7 57 15.38 W
 Ansemil, Silleda               42 44 21.56 N   8 16 12.47 W
 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
+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
+Aquis Querquennis              41 58 12 N      7 58 48 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 Triomf de Barcelona             41 23 28 N      2 10 50.02 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 Constantine            41 53 22.92 N   12 29 26.88 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
+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
+Askersund      Askersund is a town in Sweden.  58 53 00 N      14 54 00 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
+Äußerer Plauenscher Friedhof Cemetery „Äußerer Plauenscher Friedhof“ in Dresden-Plauen 51 01 11.89 N   13 42 26.03 E
 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
+A Veiga        A Veiga is a municipality in Galicia, in the province of Ourense.       42 15 01.36 N   7 01 30.25 W
 Avión Avión is a municipality in Galicia, in the province of Ourense.        42 22 23.73 N   8 15 02.64 W
+A Walk on Main Street, Ferndale, California    Ferndale is a small town in Humboldt County, California.        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
 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
+B-6/61/A-220 Z light pillbox   B-6/61/A-220Z light pillbox, Beroun     49 57 25.96 N   14 05 35.6 E
+B-7/5/A-200 Z light pillbox            49 56 28.3 N    14 07 53.68 E
+B-7/6/A-200 Z light pillbox            49 56 19.34 N   14 07 56.34 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
+Baker Glacier  Chugach National Forest, Alaska 61 04 53 N      148 21 52 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.14 W
-Basilique Notre-Dame de la Daurade             43 36 03 N      1 26 23 E
+Basilique Notre-Dame de la Daurade             36 02.88 N      1 26 22.92 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
+Basilique Saint-Sauveur de Rennes              48 06 42 N      1 40 54 W
 Beade  Beade is a municipality in Galicia, in the province of Ourense. 42 20 06.72 N   8 08 45.05 W
+Bear Lake (Alaska)     Kenai Peninsula, Alaska 60 12 03 N      149 21 11 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.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
+Bibracte       Bibracte est le nom de la cité gauloise, capitale des Eduens, qui était située au sommet du mont Beuvray (France)    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
-Bishkek                42 52 00 N      74 34 00 E
+Billings Glacier       Chugach National Forest, Alaska 60 52 55 N      148 34 28 W
+Bishkek                42 52 00.12 N   74 34 00.12 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
 Boke   Boke is a small village in the German district Paderborn; it belongs to the city of Delbrück.  51 43 48 N      8 33 43.56 E
 Bolongaropalast        The Bolongaro Palace is a baroque building in Frankfurt-Höchst.        50 06 04 N      8 33 08 E
 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
+Borovsk        Borovsk, a town established in 1358, stands between Moscow and Kaluga.  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
+Brancion       Brancion is a small fortified medieval city in Burgundy, Central France.        46 32 51 N      4 47 48.12 E
+Broad Pass     George Parks Highway, Alaska    63 19 22 N      149 10 10 W
+Brudermühlbrücke     The Brudermühlbrücke is a bridge in Munich across the Isar.   48 06 45.36 N   11 33 36 E
+Brudermühlstraße     The Brudermühlstraße is a street in Munich, part of the Mittlerer Ring around the city centre.        48 06 43.92 N   11 32 58.56 E
 Buenos Aires           34 36 13 S      58 22 54 W
 Bueu   Bueu is a municipality in Galicia, Spain in the province of Pontevedra. 42 19 24.69 N   8 47 23.1 W
-Buffalo Central Terminal       The New York Central Terminal in Buffalo, USA, was a key railroad station from 1929 to 1979.    42 53 22.44 N   78 49 51.08 W
+Bürgerhospital (Stuttgart)            48 47 34.6 N    9 10 45.95 E
 Burg Stahleck  Stahleck Castle in Bacharach is a 12th century castle occupying a commanding view of the Rhine in the Loreley valley. Nowadays it's a youth hostel.     50 03 29.49 N   7 45 56.46 E
 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  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
+Cabo Fisterra          42 52 57 N      9 16 19.92 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
+Cáceres       Cáceres is the capital of Cáceres Province, in Extremadura, Spain.    39 28 23 N      6 22 16 W
+Cadaqués              42 17 18.96 N   3 16 40.08 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
@@ -117,7 +120,8 @@ Candelabro de Paracas       The Paracas Candelabra is a well-known prehistoric geoglyp
 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
+Capela de São Nicolau         32 41 08 27.15 N        8 36 55.57 W
+Cap Glacier    Chugach National Forest, Alaska 60 56 57 N      147 54 57 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
 Carballeda de Avia     Carballeda de Avia is a municipality in Galicia, in the province of Ourense.    42 19 12.81 N   8 09 53.03 W
 Cariño        Cariño is a municipality in the province A Coruña, Galicia, Spain.    43 43 58 N      7 52 39.76 W
@@ -126,8 +130,7 @@ 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à     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
+Casa Milà             41 23 43.08 N   2 09 42.12 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
@@ -138,7 +141,7 @@ 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
 Castelo de Maceda              42 16 13.92 N   7 39 32.36 W
 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 Monterreal          42 07 32.16 N   8 51 00 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  Castillo de San Felipe is a castle in the county of Ferrol, Galicia     43 27 53 N      8 16 54 W
@@ -146,27 +149,28 @@ 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.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 Vilalba             43 17 52.8 N    7 40 56.42 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.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 Baroña              42 41 41.8 N    9 01 55.99 W
 Castro de Rei          43 12 32.14 N   7 23 58.38 W
 Catacaos               5 16 00 S       80 41 00 W
+Cataract Glacier       Chugach National Forest, Alaska 61 01 39 N      148 25 23 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 Chartres             48 26 52 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
+Cathédrale Saint-Julien du Mans       Interior        48 00 33 N      0 11 56 E
+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
 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
@@ -176,56 +180,45 @@ 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.71 W
-Chapelle de Languidou  Chapel in Plovan / Bretagne     47 54 49.13 N   4 21 09.72 W
+Chapelle de Languidou          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 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 royale Saint-Louis, Dreux             48 44 18 N      1 21 48 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
 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
-Church of Adelboden    The gothic village church of Adelboden was built in the 15th century    46 29 34.1 N    7 33 32.4 E
-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'Angers              47 28 11.67 N   0 33 33.61 W
 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 d'Azay-le-Rideau      The Château de Azay-le-Rideau is one of the most famous castles in the French Loire Valley.    47 15 33 N      0 27 58 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 Châteaudun                48 04 14 N      1 19 25 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 Condé             49 00 20 N      3 33 34 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
 Château de Domfront           48 35 39 N      0 39 09 W
+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 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 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 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
@@ -233,34 +226,49 @@ 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 des Adhémar          44 33 33.02 N   4 45 15.35 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 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
 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 Varillettes        The Château de Varillettes is a château in the Cantal.        45 01 26.76 N   3 08 53.16 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 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
+Château d'If          43 16 47.5 N    5 19 30.5 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
+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-Gaillard      Château-Gaillard is a castle in Les Andelys (Normandie, France).       49 14 17.26 N   1 24 09.91 E
+Chena Hot Springs, Alaska              65 03 10 N      146 03 19 W
+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
+Church of Adelboden    The gothic village church of Adelboden was built in the 15th century    46 29 34.1 N    7 33 32.4 E
+Church of Saints Apostles Peter and Paul in Vilnius            54 41 38.82 N   25 18 22.69 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
+Church of St. Johns in Vilnius         54 40 57.5 N    25 17 19.22 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
+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 42.84 N   2 23 39.12 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
+City Palace (Udaipur)  The City Palace in Udaipur was the royal palace of the Maharana of Mewar. The palace is located on the east bank of Lake Pichola in Udaipur, Rajasthan, India.  24 34 37.2 N    73 41 00.96 E
+Ciudad Real            38 58 59.88 N   3 55 00.12 W
+Clos Vougeot   Clos Vougeot is a famous vineyard in Burgundy, France.  47 10 29.72 N   4 57 19.74 E
+Cochrane Bay   Chugach National Forest, Alaska 60 44 18 N      148 20 06 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
+Collégiale Saint-Thiébaut de Thann           47 48 40 N      7 06 06 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
 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
 Conciergerie   The Conciergerie in the Palais de Justice, Paris, France        48 51 23 N      2 20 44 E
+Córdoba (Argentina)   Intendancy square       31 24 00 S      64 11 00 W
 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
@@ -269,41 +277,165 @@ Couvent de la Divine Providence de Saint-Jean-de-Bassel          48 48 12.12 N   6 59 32.0
 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
+Cygnus olor from Carolasee     The swans and pond are there since 1882.        51 01 59.16 N   13 45 52.69 E
 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
+Delaney Park, Anchorage, Alaska                61 12 47 N      149 54 04 W
 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       München        48 07 57.85 N   11 32 40.69 E
+Deutsches Museum Verkehrszentrum       Ganghoferstraße 29, 80339 München     48 07 57.85 N   11 32 40.69 E
 Deyrulzaferan          37 17 57.6 N    40 47 33.9 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. 21 06 29.99 N   105 57 06.31 E
+Doran Strait   Chugach National Forest, Alaska 61 04 34 N      148 11 20 W
+Dorfkirche Dabergotz           52 54 10.44 N   12 43 30.42 E
+Dorfkirche Golzow (Barnim)             52 54 41.37 N   13 48 32.69 E
+Dorfkirche Hohenfinow          52 48 38.1 N    13 55 29.08 E
+Dorfkirche Kirchlotheim                51 10 07.83 N   8 53 46.05 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
+Dorfkirche Zechow              53 03 18.01 N   12 54 44.88 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         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
-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
+É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
+École Jules Ferry de Royan            45 37 38.16 N   1 01 33.66 W
+Effnerplatz    The Effnerplatz is a square in the north of Munich in the Borough Bogenhausen.  48 09 09 N      11 36 51.12 E
+Église de la Sainte-Trinité (Lauterbourg)            48 58 30.1 N    8 10 40.77 E
+Église de l'Assomption simultanée (La Petite-Pierre)         48 51 25.2 N    7 18 55.84 E
+Église de Saint-Lothain               46 49 27.84 N   5 38 30.12 E
+Église de Saint-Paul de Frontignan    L'Église de Saint-Paul de Frontignan est une église catholique de l'ancien diocèse de Maguelone en Languedoc, France.        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 de la Dalbade               43 35 51.36 N   1 26 32.89 E
+Église Notre-Dame-de-la-Nativité (Saverne)           48 44 28.32 N   7 21 50.36 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 (Guebwiller)                47 54 20.75 N   7 12 52.56 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 du Temple Neuf (Strasbourg)                48 35 00 N      7 44 54 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 (Sélestat)                48 15 36.36 N   7 27 11.56 E
+Église protestante (Weiterswiller)            48 51 10.44 N   7 24 50.9 E
+Église Sainte-Marie-Madeleine de Rennes-le-Château           42 55 41.05 N   2 15 45.69 E
+Église Saint-Eustache de Paris                48 51 48 N      2 20 42 E
+Église Saint-Gervais-Saint-Protais            48 51 19.8 N    2 21 16.6 E
+Église Saint-Laurent (Paris)          48 52 29.45 N   2 21 29.92 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-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
+Eglise Saint-Pierre des Cuisines       L'Église Saint-Pierre des Cuisines, située rue de la Boule, à côté de la place Saint-Pierre à Toulouse, est la plus vieille église du sud-ouest de la France. Elle est construite sur une ancienne nécropole gallo-romaine du IVe siècle.      43 36 15.49 N   1 26 08.26 E
+Église Saint-Pierre-Saint-Paul de Rueil-Malmaison             48 52 35.4 N    2 10 53.15 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 Saint-Sulpice          48 51 04 N      2 20 05 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
-El Padul               37 01 27 N      3 37 36 W
+Églises St Pierre le Vieux (Strasbourg)       protestant church       48 34 58 N      7 44 24 E
+Église St Antoine de Padoue (Saverne)         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 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 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 Walburge (Walbourg)                48 53 05.51 N   7 47 21.97 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-Baptiste (Saint-Jean-Saverne)          48 46 18.7 N    7 21 48.5 E
+Église St Jean-Baptiste simultanée (Hohwiller)               48 45 12.78 N   7 27 56.77 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 protestante (Wissembourg)              49 02 18.96 N   7 56 33.36 E
+Église St Jean (Strasbourg)           48 35 04 N      7 44 25 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 Médard (Bœrsch)           48 28 40.44 N   7 26 24.4 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 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
+Eklutna Village Cemetery       Anchorage, Alaska       61 27 38 N      149 21 42 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
+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
+Esther Passage Chugach National Forest, Alaska 60 53 20 N      147 56 27 W
 European Parliament    The European Parliament is the parliament of the European Union.        48 35 51.82 N   7 46 09.82 E
+Explorer Glacier       Chugach National Forest, Alaska 60 46 34 N      148 55 03 W
 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
@@ -312,115 +444,129 @@ 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.04 N   11 17 39.19 E
+Filialkirche hl. Gotthart in Lansach, Weißenstein             46 41 12.84 N   13 42 16.56 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
+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
+Forge d'Étueffont             47 43 20.9 N    6 55 15.19 E
+Fortaleza da Nogueirosa                43 23 29.12 N   8 08 09.12 W
+Fortaleza de San Paio de Narla         43 00 25.83 N   7 49 14 W
 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 de Vézelois              47 36 01.67 N   6 54 29.41 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
-Fortaleza da Nogueirosa                43 23 29.12 N   8 08 09.12 W
-Fortaleza de San Paio de Narla         43 00 25.83 N   7 49 14 W
 Forte di Gavi  Il Forte di Gavi è una fortezza storica costruita su un preesistente castello di origine medioevale.   44 41 27.95 N   8 48 15.55 E
 Fortezza del Priamar   La fortezza del Priamar è un antico insediamento storico presente nella città ligure di Savona, Italia.       44 18 16.29 N   8 29 03.44 E
-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
+Fort Ross      Fort Ross, a former Russian fur trade outpost, located on the coast of Northern California.     38 30 51.35 N   123 14 36.88 W
 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
+Fox Island (Alaska)    Kenai Peninsula, Alaska 59 54 46 N      149 20 52 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
+Fuzhou         26 04 16 N      119 18 13 E
+Galata Bridge  Galata Bridge crosses the Golden Horn in Istanbul, Turkey       41 01 13.1 N    28 58 24.4 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
+Gardens of Nymphenburg Palace  Der Nymphenburger Schlosspark ist eines der größten und bedeutendsten Gartenkunstwerke Deutschlands.  48 09 28 N      11 29 34 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-Est              48 52 36.84 N   2 21 33.12 E
+Gare de Paris-Nord             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
+Garmischer Straße     The Garmischer Straße is a street in Munich, part of the Mittlerer Ring around the city centre.        48 07 28.2 N    11 31 14.52 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
-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.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
 Glanum         43 46 26 N      4 49 57 E
+Godwin Glacier Chugach National Forest, Alaska 60 07 45 N      149 12 52 W
 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
+Göltzschtalbrücke            50 37 21.29 N   12 14 37.46 E
+Goose Lake Park, Anchorage, Alaska             61 11 49 N      149 49 13 W
 Gorle          45 42 14 N      9 43 08 E
+Görzig (Rietz-Neuendorf)      Train station   52 14 20.25 N   14 11 48.91 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.25 E
+Gråmanstorps kyrka            56 08 59.33 N   13 06 00.4 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
+Gromo  Gromo is a town in Lombardia in Italy.  45 57 51.84 N   9 55 39 E
+Grouse Lake    Kenai Peninsula, Alaska 60 12 05 N      149 22 29 W
+Gulkana Glacier        Eastern Alaska Range, Alaska    63 14 26 N      145 28 03 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
+Haage  Train station   52 40 37.61 N   12 35 48.46 E
 Hamburger Rathaus              53 33 01 N      9 59 32 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
+Hamburg-Moorburg Sprengung Kraftwerk   Bursting of the 256 meters high chimney of the deactivated HEW power plant in Hamburg-Moorburg, Germany. The chimney was Hamburg's highest massiv building. The HEW power plant Hamburg-Moorburg was in operation from 1974 untill 2001. It was one of the biggest conventional power plant in Germany and was fired on both with natural gas and fuel oil.     53 29 24 N      9 57 06 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
+Hammer (Liebenwalde)   Former town hall        52 52 54.39 N   13 26 32.69 E
+Hansestaden Visby      The Hanseatic town Visby is the description of Visby, Sweden, from the UNESCO World Heritage Committee. 57 38 20 N      18 17 40 E
+Hans Paasche   Hans Paasche (3 April 1881 – 21 May 1920) was a German politician and pacifist.       52 59 48.32 N   15 58 47.1 E
+Harriman Fjord Chugach National Forest, Alaska 61 02 20 N      148 19 17 W
 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
+Heckenstallerstraße   The Heckenstallerstraße is a street in Munich, part of the Mittlerer Ring around the city centre.      48 06 35.64 N   11 31 41.88 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
 HMS Otus       The HMS Otus is a british Oberon class submarine. It serves today as a museum Sassnitz harbour. 54 30 43.13 N   13 38 29.79 E
-Holy Trinity Cathedral in Odessa               46 28 34.36 N   30 44 18.43 E
+Holgate Arm    Kenai Fjords National Park, Alaska      59 49 56 N      149 47 56 W
+Holy Trinity Cathedral in Odessa       Holy Trinity Cathedral in Odessa        46 28 33.96 N   30 44 17.16 E
+Hope Highway, Alaska   Kenai Peninsula, Alaska 60 46 50 N      149 25 50 W
 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
-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 d'Ulmo          43 35 51 N      1 26 59.28 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
+Hübners Mühle in Werder (Havel)      The fire ruin of the windmill of miller and baker named Hübner in Werder (Havel)       52 22 28.38 N   12 55 15.28 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.08 N   15 10 10.92 E
+Igreja de São Martinho de Aldoar              41 10 14.59 N   8 40 13.84 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 Santa María de Cambre               43 17 31.92 N   8 20 34.08 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
 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
-Isfahan اصفهان           32 39 05 N      51 40 45 E
+Isabel Pass    Richardson Highway, Alaska      63 11 15 N      145 33 28 W
+Isarring       The Isarring is a street in Munich, part of the Mittlerer Ring around the city centre.  48 09 36.72 N   11 36 03.96 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
 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
+Jaroměř      Pond    50 21 21.6 N    15 55 15.6 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
+José María Acuña López     José María Acuña López, born in Pontevedra (Spain) on April 4, 1903 and died on 4 June 1991 in Vigo (Spain), was a spanish sculptor.        42 19 46.38 N   8 34 04.43 W
 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
+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
 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
+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
-Kathmandu              27 43 00 N      85 22 00 E
+Kathmandu              27 43 12 N      85 22 12 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
+Korsberga kyrka, Småland              57 18 25.2 N    15 07 25.6 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
@@ -429,32 +575,31 @@ 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 09 35 N       101 42 00 E
+Kuala Lumpur           3 08 52.08 N    101 41 43.08 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
+Lake Louise, Alaska    Borough di Matanuska-Susitna, Alaska    62 19 26 N      146 32 56 W
 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          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
+Large Hadron Collider          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
 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
+Learnard Glacier       Chugach National Forest, Alaska 60 48 44 N      148 42 55 W
+Leiro  Leiro is a municipality in Galicia, in the province of Ourense. 42 22 11.51 N   8 07 27.42 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
+Le Train Bleu          48 50 42 N      2 22 24 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
-Loschwitzer Friedhof   Cemetery "Loschwitzer Friedhof" in Dresden-Loschwitz    51 02 46 N      13 49 18.98 E
+Lima   Lima    12 03 00 S      77 02 00 W
+Loro Parque            28 24 29.88 N   16 33 52.74 W
+Loschwitzer Friedhof   Cemetery „Loschwitzer Friedhof“ in Dresden-Loschwitz        51 02 46 N      13 49 18.98 E
+Lowell Point, Alaska   Kenai Peninsula, Alaska 60 04 18 N      149 26 37 W
 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
@@ -468,7 +613,10 @@ 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
 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
+Margaret Eagan Sullivan Park, Anchorage, Alaska                61 12 31 N      149 55 16 W
+Marienkirche Witzwort          54 23 58.86 N   8 59 06.17 E
 Marín         42 23 31.28 N   8 42 16.58 W
+Mary's Tomb            31 46 48.5 N    35 14 21.41 E
 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
@@ -476,110 +624,112 @@ 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
 Melón Melón is a municipality in Galicia, in the province of Ourense.        42 15 26.97 N   8 13 01.51 W
+Mérida (Spain)                38 54 56.88 N   6 19 59.88 W
 Mii-dera       Mii-dera 三井寺, formally Onjōji 園城寺, is a Tendai Buddhist temple in the city of Otsu, Shiga Prefecture, Japan.       35 00 48.09 N   135 51 10.26 E
 Millennium Town Park   The Millennium Town Park is a public park in Saint Helier, Jersey.      49 11 15.01 N   2 06 06.95 W
-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
+Minden Minden is a German city in North Rhine-Westphalia.      52 17 20.18 N   8 55 04.19 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          38 10 20 S      146 16 04 E
+Möja kyrka            59 24 18.8 N    18 52 53 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
 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.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
+Mońki Railway station 53 24 00 N      22 47 00 E
+Monte Amiata   Il Monte Amiata è un monte situato nella Toscana.      42 53 15.9 N    11 37 27.73 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
-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
+Montevideo     Montevideo is the capital and largest city of Uruguay.  34 52 01 S      56 10 00 W
+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.08 N   2 19 49.08 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.19 W
+Mössingen             48 24 23 N      9 03 27 E
 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
+Mount Muir, Alaska     Chugach Mountains, Alaska       61 06 29 N      148 22 42 W
 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
+Mühlen am Löbauer Wasser             51 12 02.21 N   14 39 21.09 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
+Museo Archeologico Regionale Paolo Orsi        Archaeological Museum Paolo Orsi in Syracuse    37 04 34.36 N   15 17 10.89 E
 Museo Civico d'Arte Antica di Torino   Il Museo Civico d'Arte Antica è un polo museale situato a Torino presso Palazzo Madama.        45 04 15.95 N   7 41 07.72 E
 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
-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
+Naturschutzgebiet „Königsbrücker Heide“          51 20 07.58 N   13 52 06.67 E
 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               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
 Noorderplantsoen       The Noorderplantsoen is a park in the Dutch city of Groningen.  53 13 25 N      6 33 20 E
+Nordwestbahnhof, Vienna        Emergency quarters from Northwest Railway Wagon 13 46.81 N      16 22 58.91 E
+Nulbay Park, Anchorage, Alaska         61 12 57 N      149 54 36 W
 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.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.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
+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.47 W
 Ocean Park, Hong Kong          22 14 45.1 N    114 10 33.3 E
+O Ézaro, Dumbría             42 54 38.2 N    9 07 54.12 W
+O Grove                42 29 34.29 N   8 52 04.9 W
 Ohr Somayach Synagogue Ohr Somayach Synagogue, the main synagogue in Odessa, Ukraine   46 28 40.55 N   30 44 22.13 E
 Oia    Oia is a municipality in Galicia, Spain in the province of Pontevedra.  42 00 06.45 N   8 52 30.54 W
+Oímbra                41 53 07.94 N   7 28 19.78 W
 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
+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
 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
+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
 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.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.       41 55 26.26 N   42 00 33.84 E
-Oímbra                41 53 07.94 N   7 28 19.78 W
 Palace and park of Versailles          48 48 15.85 N   2 07 23.38 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
 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 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
+Palais ducal de Nevers The Palais Ducal in Nevers, France      46 59 18 N      3 09 30 E
 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