diff --git a/build.xml b/build.xml
index 572c9df..c74d3ef 100644
--- a/build.xml
+++ b/build.xml
@@ -8,9 +8,9 @@
     <!-- Configure these properties (replace "..." accordingly).
          See https://josm.openstreetmap.de/wiki/DevelopersGuide/DevelopingPlugins
     -->
-    <property name="plugin.author" value="Oliver Wieland"/>
+    <property name="plugin.author" value="Oliver Wieland, Harald Hetzner"/>
     <property name="plugin.class" value="org.openstreetmap.josm.plugins.elevation.ElevationProfilePlugin"/>
-    <property name="plugin.description" value="Shows the elevation profile and some statistical data of a GPX track."/>
+    <property name="plugin.description" value="Shows the elevation at the location on the map as well as the elevation profile and some statistical data of a GPX track."/>
     <property name="plugin.icon" value="images/elevation.png"/>
     <property name="plugin.link" value="https://wiki.openstreetmap.org/wiki/JOSM/Plugins/ElevationProfile"/>
 
diff --git a/images/preferences/elevation.svg b/images/preferences/elevation.svg
new file mode 100644
index 0000000..4115718
--- /dev/null
+++ b/images/preferences/elevation.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg
+   width="24"
+   height="24"
+   viewBox="0 0 24 24">
+  <g
+     transform="translate(0,-1037.3622)">
+    <path
+       style="fill:#89a02c;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 23,1055.3622 H 1 l 5,-7 5,4 6,-10 6,10 z" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 1,1055.3622 5,-7 5,4 6,-10 6,10" />
+  </g>
+</svg>
diff --git a/images/statusline/ele.svg b/images/statusline/ele.svg
new file mode 100644
index 0000000..969f2d2
--- /dev/null
+++ b/images/statusline/ele.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg
+   version="1.1"
+   width="18px"
+   height="18px"
+   viewBox="0 0 18 18"
+   fill="none" >
+  <path
+     d="M 9,1 V 17"
+     stroke="#ee4422"
+     stroke-width="1.88562" />
+  <path
+     style="fill:none;stroke:#000000;stroke-width:0.93133px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     d="M 0.99621412,16.121325 9,2.3402182 17.003786,16.121325 Z" />
+</svg>
diff --git a/src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java b/src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java
index 6c4de87..990f540 100644
--- a/src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java
+++ b/src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java
@@ -168,7 +168,7 @@ public final class ElevationHelper {
         if (ll != null) {
             // Try to read data from SRTM file
             // TODO: Option to switch this off
-            double eleHgt = HgtReader.getElevationFromHgt(ll);
+            double eleHgt = HgtReader.getInstance().getElevationFromHgt(ll);
 
             if (isValidElevation(eleHgt)) {
                 return eleHgt;
@@ -184,7 +184,7 @@ public final class ElevationHelper {
      */
     public static Optional<Bounds> getBounds(ILatLon location) {
         if (location != null) {
-            return HgtReader.getBounds(location);
+            return HgtReader.getInstance().getBounds(location);
         }
         return Optional.empty();
     }
diff --git a/src/org/openstreetmap/josm/plugins/elevation/ElevationPreferences.java b/src/org/openstreetmap/josm/plugins/elevation/ElevationPreferences.java
new file mode 100644
index 0000000..ba9d166
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/ElevationPreferences.java
@@ -0,0 +1,69 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.elevation;
+
+import java.io.File;
+import java.nio.file.Paths;
+
+import org.openstreetmap.josm.data.Preferences;
+
+/**
+ * Property keys and default values for elevation data preferences.
+ *
+ * @author Harald Hetzner
+ *
+ */
+public class ElevationPreferences {
+
+    /** Property key for enabling or disabling use of elevation data. */
+    public static final String ELEVATION_ENABLED = "elevation.enabled";
+
+    /** Property key for enabling or disabling elevation profile layer. */
+    public static final String ELEVATION_PROFILE_ENABLED = "elevation.profile.enabled";
+
+    /** Property key for enabling or disabling automatic download of elevation data. */
+    public static final String ELEVATION_AUTO_DOWNLOAD_ENABLED = "elevation.autodownload";
+
+    /**
+     * Property key for authentication bearer token for SRTM HGT server.
+     * @see HgtDownloader
+     */
+    public static final String ELEVATION_SERVER_AUTH_BEARER = "elevation.hgt.server.auth.bearer";
+
+    /** Default property value for enabling use of elevation data: {@code false}. */
+    public static final boolean DEFAULT_ELEVATION_ENABLED = false;
+
+    /** Default property value for enabling the elevation profile layer: {@code false}. */
+    public static final boolean DEFAULT_ELEVATION_PROFILE_ENABLED = false;
+
+    /** Default property value for enabling automatic download of elevation data: {@code false}. */
+    public static final boolean DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED = false;
+
+    /** Default property value for authentication bearer token for SRTM HGT server: Empty {@code String}. */
+    public static final String DEFAULT_ELEVATION_SERVER_AUTH_BEARER = "";
+
+    /** Default path, where SRTM3 HGT files need to be placed, respectively to which they will be downloaded. */
+    public static final File DEFAULT_HGT_DIRECTORY = Paths.get(Preferences.main().getDirs().getCacheDirectory(true).toString(), "elevation", "SRTM3").toFile();
+
+    /**
+     * URL of <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
+     * where users need to register and create an authorization bearer token in order to download elevation
+     * data from {@link ElevationPreferences#HGT_SERVER_BASE_URL}.
+     */
+    public static final String HGT_SERVER_REGISTRATION_URL = "https://urs.earthdata.nasa.gov/users/new/";
+
+    /**
+     * URL of
+     * <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>
+     * where SRTM3 HGT files can be downloaded.
+     *
+     * Requires registration at {@link ElevationPreferences#HGT_SERVER_REGISTRATION_URL}.
+     */
+    public static final String HGT_SERVER_BASE_URL = "https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/";
+
+    /**
+     * Prefix of compressed as-downloaded HGT files.
+     */
+    public static final String SRTM3_HGT_ZIP_FILE_PREFIX = ".SRTMGL3.hgt.zip";
+
+    private ElevationPreferences() {}
+}
diff --git a/src/org/openstreetmap/josm/plugins/elevation/ElevationProfilePlugin.java b/src/org/openstreetmap/josm/plugins/elevation/ElevationProfilePlugin.java
index d43cae7..d9a3100 100644
--- a/src/org/openstreetmap/josm/plugins/elevation/ElevationProfilePlugin.java
+++ b/src/org/openstreetmap/josm/plugins/elevation/ElevationProfilePlugin.java
@@ -10,21 +10,38 @@ import org.openstreetmap.josm.gui.IconToggleButton;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MainMenu;
 import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.plugins.Plugin;
 import org.openstreetmap.josm.plugins.PluginInformation;
 import org.openstreetmap.josm.plugins.elevation.actions.AddElevationLayerAction;
+import org.openstreetmap.josm.plugins.elevation.gui.ElevationPreference;
 import org.openstreetmap.josm.plugins.elevation.gui.ElevationProfileDialog;
 import org.openstreetmap.josm.plugins.elevation.gui.ElevationProfileLayer;
+import org.openstreetmap.josm.plugins.elevation.gui.LocalElevationLabel;
+import org.openstreetmap.josm.spi.preferences.Config;
 
 /**
  * Plugin class for displaying an elevation profile of the tracks.
  * @author Oliver Wieland &lt;oliver.wieland@online.de&gt;
+ * @author Harald Hetzner
  *
  */
 public class ElevationProfilePlugin extends Plugin {
 
     private static ElevationProfileLayer currentLayer;
 
+    private ElevationPreference preference = null;
+
+    private boolean elevationEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED);
+
+    private LocalElevationLabel localElevationLabel = null;
+
+    private static ElevationProfilePlugin instance = null;
+
+    public static ElevationProfilePlugin getInstance() {
+        return instance;
+    }
+
     /**
      * Initializes the plugin.
      * @param info Context information about the plugin.
@@ -37,6 +54,8 @@ public class ElevationProfilePlugin extends Plugin {
 
         // TODO: Disable this view as long as it is not stable
         MainMenu.add(MainApplication.getMenu().imagerySubMenu, new AddElevationLayerAction(), false, 0);
+
+        instance = this;
     }
 
     /**
@@ -47,15 +66,19 @@ public class ElevationProfilePlugin extends Plugin {
     @Override
     public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
         super.mapFrameInitialized(oldFrame, newFrame);
+        setElevationEnabled(elevationEnabled, newFrame);
+    }
 
-        if (newFrame != null) {
-            ElevationMapMode eleMode = new ElevationMapMode("Elevation profile");
-            newFrame.addMapMode(new IconToggleButton(eleMode));
-            ElevationProfileDialog eleProfileDlg = new ElevationProfileDialog();
-            eleProfileDlg.addModelListener(eleMode);
-            eleProfileDlg.setProfileLayer(getCurrentLayer());
-            newFrame.addToggleDialog(eleProfileDlg);
-        }
+    /**
+     * Called in the preferences dialog to create a preferences page for the plugin,
+     * if any available.
+     * @return the preferences dialog, or {@code null}
+     */
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        if (preference == null)
+            preference = new ElevationPreference();
+        return preference;
     }
 
     /**
@@ -124,4 +147,52 @@ public class ElevationProfilePlugin extends Plugin {
         }
                 );
     }
+
+    public boolean isElevationEnabled() {
+        return elevationEnabled;
+    }
+
+    /**
+     * Enable or disable displaying elevation at the position of the mouse pointer.
+     * @param enabled If {@code true} displaying of elevation is enabled, else disabled.
+     * @see HgtReader
+     */
+    public void setElevationEnabled(boolean enabled) {
+        setElevationEnabled(enabled, MainApplication.getMap());
+    }
+
+    private void setElevationEnabled(boolean enabled, MapFrame mapFrame) {
+        if (localElevationLabel == null &&  mapFrame != null) {
+            localElevationLabel = new LocalElevationLabel(mapFrame);
+            localElevationLabel.setVisible(enabled);
+        }
+        if (enabled) {
+            // Elevation profile
+            boolean elevationProfileEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED,
+                    ElevationPreferences.DEFAULT_ELEVATION_PROFILE_ENABLED);
+            if (elevationProfileEnabled) {
+                ElevationMapMode eleMode = new ElevationMapMode("Elevation profile");
+                mapFrame.addMapMode(new IconToggleButton(eleMode));
+                ElevationProfileDialog eleProfileDlg = new ElevationProfileDialog();
+                eleProfileDlg.addModelListener(eleMode);
+                eleProfileDlg.setProfileLayer(getCurrentLayer());
+                mapFrame.addToggleDialog(eleProfileDlg);
+            }
+
+            // Auto-download of HGT files
+            boolean elevationAutoDownloadEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED,
+                    ElevationPreferences.DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED);
+            // If enabled, HgtDownloader used by HgtReader will by itself read the authentication bearer token from the preferences
+            HgtReader.getInstance().setAutoDownloadEnabled(elevationAutoDownloadEnabled);
+        }
+        else {
+            if (localElevationLabel != null) {
+                localElevationLabel.destroy();
+                localElevationLabel = null;
+            }
+            HgtReader.destroyInstance();
+        }
+        elevationEnabled = enabled;
+    }
+
 }
diff --git a/src/org/openstreetmap/josm/plugins/elevation/HgtDownloadListener.java b/src/org/openstreetmap/josm/plugins/elevation/HgtDownloadListener.java
new file mode 100644
index 0000000..5b2f924
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/HgtDownloadListener.java
@@ -0,0 +1,46 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.elevation;
+
+import java.io.File;
+
+import org.openstreetmap.josm.data.coor.ILatLon;
+
+/**
+ *
+ * @author Harald Hetzner
+ *
+ */
+public interface HgtDownloadListener {
+
+    /**
+     * Informs the implementing class that downloading of HGT data for the given
+     * coordinates has started.
+     *
+     * To be called by the thread downloading as soon as downloading actually started.
+     *
+     * @param latLon The coordinates for which the HGT data is now being downloaded.
+     */
+    public void hgtFileDownloading(ILatLon latLon);
+
+    /**
+     * Informs the implementing class that HGT data for the given coordinates was
+     * successfully downloaded.
+     *
+     * To be called by the thread downloading as soon as the download finished.
+     *
+     * @param latLon The coordinates for which the download of HGT data succeeded.
+     * @param hgtFile The downloaded HGT file.
+     */
+    public void hgtFileDownloadSucceeded(ILatLon latLon, File hgtFile);
+
+    /**
+     * Informs the implementing class that downloading HGT data for the given
+     * coordinates failed.
+     *
+     * To be called by the thread downloading as soon as downloading fails.
+     *
+     * @param latLon The coordinates for which the download of HGT data failed.
+     */
+    public void hgtFileDownloadFailed(ILatLon latLon);
+
+}
diff --git a/src/org/openstreetmap/josm/plugins/elevation/HgtDownloader.java b/src/org/openstreetmap/josm/plugins/elevation/HgtDownloader.java
new file mode 100644
index 0000000..9dc55fd
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/HgtDownloader.java
@@ -0,0 +1,176 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.elevation;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.LinkedList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.openstreetmap.josm.data.coor.ILatLon;
+import org.openstreetmap.josm.gui.io.DownloadFileTask;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.HttpClient;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Class {@code HgtDownloader} downloads SRTM HGT (Shuttle Radar Topography Mission Height) files with elevation data.
+ * Currently this class is restricted to a resolution of 3 arc seconds (SRTM3).
+ *
+ * SRTM3 HGT files are available at
+ * <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>.
+ *
+ * In order to access these files, registration at <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
+ * and creating an authorization bearer token on this site are required.
+ *
+ * @author Harald Hetzner
+ * @see HgtReader
+ *
+ */
+public class HgtDownloader {
+
+    private final URL baseUrl;
+    private File hgtDirectory;
+
+    private String authHeader;
+
+    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
+
+    private final LinkedList<HgtDownloadListener> downloadListeners = new LinkedList<HgtDownloadListener>();
+
+    private boolean uncompressDownloadedFiles = false;
+
+    public HgtDownloader(File hgtDirectory, String url, String bearer) throws MalformedURLException {
+        // May throw MalformedURLException
+        this.baseUrl = new URL(url);
+        // https://stackoverflow.com/questions/38085964/authorization-bearer-token-in-httpclient
+        if (!bearer.equals(""))
+            authHeader = "Bearer " + bearer;
+        else
+            authHeader = null;
+        this.hgtDirectory = hgtDirectory;
+    }
+
+    public HgtDownloader(File hgtDirectory) throws MalformedURLException {
+        this(hgtDirectory, ElevationPreferences.HGT_SERVER_BASE_URL,
+                Config.getPref().get(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, ElevationPreferences.DEFAULT_ELEVATION_SERVER_AUTH_BEARER));
+    }
+
+    public void setHgtDirectory(File hgtDirectory) {
+        this.hgtDirectory = hgtDirectory;
+    }
+
+    public void setUncompressDownloadedFiles(boolean b) {
+        uncompressDownloadedFiles = b;
+    }
+
+    public void addDownloadListener(HgtDownloadListener listener) {
+        if (!downloadListeners.contains(listener))
+            downloadListeners.add(listener);
+    }
+
+    public void downloadHgtFile(ILatLon latLon) {
+        EXECUTOR.submit(new DownloadHgtFileTask(latLon));
+    }
+
+    /**
+     * Gets the associated HGT file name for the given coordinate. Usually the
+     * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude
+     * without decimals and <i>mmm</i> is the longitude.
+     *
+     * @param latLon The coordinate to get the filename for
+     * @return The file name of the HGT file.
+     */
+    public static String getSrtm3HgtZipFileName(ILatLon latLon) {
+        return HgtReader.getHgtPrefix(latLon) + ElevationPreferences.SRTM3_HGT_ZIP_FILE_PREFIX;
+    }
+
+    private class DownloadHgtFileTask implements Runnable {
+
+        private final ILatLon latLon;
+
+
+        public DownloadHgtFileTask(ILatLon latLon) {
+            this.latLon = latLon;
+        }
+
+        @Override
+        public void run() {
+            downloading();
+            String hgtDirectoryPath = HgtDownloader.this.hgtDirectory.toString();
+            String hgtZipFileName = HgtDownloader.getSrtm3HgtZipFileName(latLon);
+            File hgtFile = null;
+
+            URL url = null;
+            try {
+                url = new URL(HgtDownloader.this.baseUrl + hgtZipFileName);
+            } catch (MalformedURLException e) {
+                downloadFailed();
+                return;
+            }
+            HttpClient httpClient = HttpClient.create(url);
+            if (authHeader != null)
+                httpClient.setHeader("Authorization", authHeader);
+            HttpClient.Response response = null;
+            try {
+                response = httpClient.connect();
+                //Logging.info("Elevation: HGT server responded: " + response.getResponseCode() + " " +  response.getResponseMessage());
+                // https://urs.earthdata.nasa.gov/documentation/for_users/data_access/java
+                if (response.getResponseCode() != 200) {
+                    downloadFailed();
+                    return;
+                }
+                InputStream in = response.getContent();
+                Path downloadedZipFile = Paths.get(hgtDirectoryPath, hgtZipFileName);
+                Files.copy(in, downloadedZipFile, StandardCopyOption.REPLACE_EXISTING);
+                if (uncompressDownloadedFiles) {
+                    DownloadFileTask.unzipFileRecursively(downloadedZipFile.toFile(), hgtDirectoryPath);
+                    Files.delete(downloadedZipFile);
+                    // Determine the file, which was uncompressed
+                    hgtFile = HgtReader.getInstance().getHgtFile(latLon);
+                }
+                else {
+                    hgtFile = downloadedZipFile.toFile();
+                }
+            } catch (IOException e) {
+                if (response != null)
+                    Logging.error("Elevation: HGT server responded: " + response.getResponseCode() + " " + response.getResponseMessage());
+                Logging.error("Elevation: Downloading HGT file " + hgtZipFileName + " failed: " + e.toString());
+                downloadFailed();
+                return;
+            }
+
+            // This would happen, if the downloaded file was uncompressed, but it does not contain an appropriately named file (with HGT prefix)
+            if (hgtFile == null) {
+                Logging.error("Elevation: Downloaded compressed file " + hgtZipFileName + " did not contain a file with the expected HGT prefix!");
+                downloadFailed();
+                return;
+            }
+
+            Logging.info("Elevation: Successfully downloaded HGT file " + hgtFile.getName() + " to HGT directory: " + HgtDownloader.this.hgtDirectory.toString());
+            downloadSucceeded(hgtFile);
+        }
+
+        private void downloading() {
+            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
+                listener.hgtFileDownloading(latLon);
+        }
+
+        private void downloadSucceeded(File hgtFile) {
+            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
+                listener.hgtFileDownloadSucceeded(latLon, hgtFile);
+        }
+
+        private void downloadFailed() {
+            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
+                listener.hgtFileDownloadFailed(latLon);
+        }
+    }
+}
diff --git a/src/org/openstreetmap/josm/plugins/elevation/HgtFileImporter.java b/src/org/openstreetmap/josm/plugins/elevation/HgtFileImporter.java
index b74d19c..72dbd6a 100644
--- a/src/org/openstreetmap/josm/plugins/elevation/HgtFileImporter.java
+++ b/src/org/openstreetmap/josm/plugins/elevation/HgtFileImporter.java
@@ -25,7 +25,7 @@ public class HgtFileImporter extends FileImporter {
 
     @Override
     public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
-        Bounds bounds = HgtReader.read(file);
+        Bounds bounds = HgtReader.getInstance().read(file);
         if (bounds != null && MainApplication.getMap() != null && MainApplication.getMap().mapView != null)
             MainApplication.getMap().mapView.zoomTo(bounds);
     }
diff --git a/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java b/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java
index 97a565c..3f31753 100644
--- a/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java
+++ b/src/org/openstreetmap/josm/plugins/elevation/HgtReader.java
@@ -5,106 +5,209 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.MalformedURLException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.commons.compress.utils.IOUtils;
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.coor.ILatLon;
 import org.openstreetmap.josm.io.Compression;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.Logging;
 
 /**
- *  Class HgtReader reads data from SRTM HGT files. Currently this class is restricted to a resolution of 3 arc seconds.
+ *  Class {@code HgtReader} reads elevation data from SRTM HGT (Shuttle Radar Topography Mission Height) files.
+ *  Currently this class is restricted to a resolution of 3 arc seconds (SRTM3).
+ *
+ *  SRTM3 HGT files are available at
+ *  <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>.
+ *
+ *  In order to access these files, registration at <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
+ *  and creating an authorization bearer token on this site are required.
  *
- *  SRTM data files are available at the <a href="http://dds.cr.usgs.gov/srtm/version2_1/SRTM3">NASA SRTM site</a>
  *  @author Oliver Wieland &lt;oliver.wieland@online.de&gt;
+ *  @author Harald Hetzner
+ *  @see HgtDownloader
  */
-public class HgtReader {
+public class HgtReader implements HgtDownloadListener {
     private static final int SRTM_EXTENT = 1; // degree
-    private static final List<String> COMPRESSION_EXT = Arrays.asList("xz", "gzip", "zip", "bz", "bz2");
 
     public static final String HGT_EXT = ".hgt";
 
+    public static final Pattern HGT_PREFIX_PATTERN = Pattern.compile("^(([NS])(\\d{2})([EW])(\\d{3})).+");
+
     // alter these values for different SRTM resolutions
     public static final int HGT_VOID = Short.MIN_VALUE; // magic number which indicates 'void data' in HGT file
 
-    private static final HashMap<String, short[][]> cache = new HashMap<>();
+    private final HashMap<String, HgtCacheData> cache = new HashMap<>();
 
-    public static double getElevationFromHgt(ILatLon coor) {
-        try {
-            String file = getHgtFileName(coor);
-            // given area in cache?
-            if (!cache.containsKey(file)) {
-
-                // fill initial cache value. If no file is found, then
-                // we use it as a marker to indicate 'file has been searched
-                // but is not there'
-                cache.put(file, null);
-                // Try all resource directories
-                for (String location : Preferences.getAllPossiblePreferenceDirs()) {
-                    String fullPath = new File(location + File.separator + "elevation", file).getPath();
-                    File f = new File(fullPath);
-                    if (!f.exists()) {
-                        for (String ext : COMPRESSION_EXT) {
-                            f = new File(fullPath + "." + ext);
-                            if (f.exists()) break;
-                        }
+    private static HgtReader hgtReader = null;
+
+    private File hgtDirectory = null;
+    private boolean autoDownloadEnabled = false;
+    private HgtDownloader hgtDownloader = null;
+
+
+    public static HgtReader getInstance() {
+        if (hgtReader == null)
+            hgtReader = new HgtReader();
+        return hgtReader;
+    }
+
+    public static void destroyInstance() {
+        hgtReader = null;
+    }
+
+    private HgtReader() {
+        this(ElevationPreferences.DEFAULT_HGT_DIRECTORY);
+    }
+
+    private HgtReader(File hgtDirectory) {
+        setHgtDirectory(hgtDirectory);
+    }
+
+    public File getHgtDirectory() {
+        return hgtDirectory;
+    }
+
+    public void setHgtDirectory(File hgtDirectory) {
+        if (!hgtDirectory.exists() && hgtDirectory.mkdirs())
+            Logging.info("Elevation: Created directory for HGT files: " + hgtDirectory.toString());
+        if (hgtDirectory.isDirectory()) {
+            this.hgtDirectory = hgtDirectory;
+            Logging.info("Elevation: Set directory for HGT files to: " + hgtDirectory.toString());
+        }
+        else {
+            Logging.error("Elevation: Could not create directory for HGT files: " + hgtDirectory.toString());
+            hgtDirectory = null;
+        }
+        if (hgtDownloader != null)
+            hgtDownloader.setHgtDirectory(hgtDirectory);
+    }
+
+    public void setAutoDownloadEnabled(boolean enabled) {
+        if (autoDownloadEnabled == enabled)
+            return;
+        if (enabled) {
+            if (hgtDirectory != null) {
+                if (hgtDownloader == null)
+                    try {
+                        hgtDownloader = new HgtDownloader(hgtDirectory);
+                        hgtDownloader.addDownloadListener(this);
+                    } catch (MalformedURLException e) {
+                        autoDownloadEnabled = false;
+                        Logging.error("Elevation: Cannot enable auto-downloading: " + e.toString());
+                        return;
                     }
-                    if (f.exists()) {
-                        read(f);
-                        break;
+                else
+                    hgtDownloader.setHgtDirectory(hgtDirectory);
+                autoDownloadEnabled = true;
+                Logging.info("Elevation: Enabled auto-downloading of HGT files to " + hgtDirectory.toString());
+            }
+            else {
+                hgtDownloader = null;
+                autoDownloadEnabled = false;
+                Logging.error("Elevation: Cannot enable auto-downloading as directory for HGT files was not set");
+            }
+        }
+        else {
+            hgtDownloader = null;
+            autoDownloadEnabled = false;
+            Logging.info("Elevation: Disabled auto-downloading of HGT files");
+        }
+    }
+
+    /**
+     * Returns the elevation at the location of the provided coordinate.
+     * If there is not HGT file with elevation data for this location and
+     * <code>autoDownload</code> is enabled, it will be attempted to download
+     * the HGT file.
+     *
+     * @param latLon The location at which the elevation is of interest.
+     * @return The elevation at the provided location or {@link ElevationHelper#NO_ELEVATION ElevationHelper.NO_ELEVATION}
+     *         if no HGT file elevation data for the location is available at present.
+     */
+    public double getElevationFromHgt(ILatLon latLon) {
+        String hgtPrefix = getHgtPrefix(latLon);
+        HgtCacheData hgtCacheData;
+
+        synchronized(cache) {
+            hgtCacheData = cache.get(hgtPrefix);
+            // data not in cache
+            if (hgtCacheData == null) {
+                File hgtFile = getHgtFile(latLon);
+                // If a HGT file with the data exists locally, read it in
+                if (hgtFile != null) {
+                    Logging.info("Elevation: Caching data for HGT prefix " + hgtPrefix + " from file " + hgtFile.getAbsolutePath());
+                    short[][] data = null;
+                    try {
+                        data = readHgtFile(hgtFile.toString());
+                    } catch (FileNotFoundException e) {
+                        Logging.error("Elevation: Getting elevation from HGT file " + hgtFile.getAbsolutePath() + " failed: => " + e.getMessage());
+                        // no problem... file not there
+                        return ElevationHelper.NO_ELEVATION;
+                    } catch (IOException ioe) {
+                        // oops...
+                        Logging.error(ioe);
+                        // fallback
+                        return ElevationHelper.NO_ELEVATION;
                     }
+                    hgtCacheData = new HgtCacheData(data);
+                    cache.put(hgtPrefix, hgtCacheData);
+                }
+                // Otherwise, put an empty data set with status "missing" into the cache
+                else {
+                    hgtCacheData = new HgtCacheData();
+                    cache.put(hgtPrefix, hgtCacheData);
                 }
             }
-
-            // read elevation value
-            return readElevation(coor, file);
-        } catch (FileNotFoundException e) {
-            Logging.error("Get elevation from HGT " + coor + " failed: => " + e.getMessage());
-            // no problem... file not there
-            return ElevationHelper.NO_ELEVATION;
-        } catch (Exception ioe) {
-            // oops...
-            Logging.error(ioe);
-            // fallback
-            return ElevationHelper.NO_ELEVATION;
+            // Read elevation value if HGT data is available
+            else if (hgtCacheData.getStatus() == HgtCacheData.Status.VALID) {
+                return readElevationFromCache(latLon);
+            }
+            // If the HGT file with the relevant elevation data is missing and auto-downloading is enabled, try to download it
+            else if (hgtCacheData.getStatus() == HgtCacheData.Status.MISSING && autoDownloadEnabled) {
+                hgtCacheData.setDownloadScheduled();
+                hgtDownloader.downloadHgtFile(latLon);
+            }
         }
+        // If not valid elevation data could be returned, return no elevation
+        return ElevationHelper.NO_ELEVATION;
     }
 
-    public static Bounds read(File file) throws IOException {
-        String location = file.getName();
-        for (String ext : COMPRESSION_EXT) {
-            location = location.replaceAll("\\." + ext + "$", "");
-        }
-        short[][] sb = readHgtFile(file.getPath());
-        // Overwrite the cache file (assume that is desired)
-        cache.put(location, sb);
-        Pattern pattern = Pattern.compile("([NS])(\\d{2})([EW])(\\d{3})");
-        Matcher matcher = pattern.matcher(location);
-        if (matcher.lookingAt()) {
-            int lat = ("S".equals(matcher.group(1)) ? -1 : 1) * Integer.parseInt(matcher.group(2));
-            int lon = ("W".equals(matcher.group(3)) ? -1 : 1) * Integer.parseInt(matcher.group(4));
+    public Bounds read(File file) throws IOException {
+        Matcher matcher = HGT_PREFIX_PATTERN.matcher(file.getName());
+        if (matcher.matches()) {
+            String hgtPrefix = matcher.group(1);
+            short[][] data = readHgtFile(file.getPath());
+            HgtCacheData hgtCacheData = new HgtCacheData(data);
+            // Overwrite the cache file (assume that is desired)
+            synchronized (cache) {
+                cache.put(hgtPrefix, hgtCacheData);
+            }
+            int lat = ("S".equals(matcher.group(2)) ? -1 : 1) * Integer.parseInt(matcher.group(3));
+            int lon = ("W".equals(matcher.group(4)) ? -1 : 1) * Integer.parseInt(matcher.group(5));
             return new Bounds(lat, lon, lat + 1, lon + 1);
         }
         return null;
     }
 
-    private static short[][] readHgtFile(String file) throws IOException {
-        CheckParameterUtil.ensureParameterNotNull(file);
+    private static short[][] readHgtFile(String fileName) throws IOException {
+        CheckParameterUtil.ensureParameterNotNull(fileName);
 
         short[][] data = null;
 
-        try (InputStream fis = Compression.getUncompressedFileInputStream(Paths.get(file))) {
+        try (InputStream fis = Compression.getUncompressedFileInputStream(Paths.get(fileName))) {
             // choose the right endianness
             ByteBuffer bb = ByteBuffer.wrap(IOUtils.toByteArray(fis));
             //System.out.println(Arrays.toString(bb.array()));
@@ -130,55 +233,59 @@ public class HgtReader {
      * Reads the elevation value for the given coordinate.
      *
      * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>
-     * @param coor the coordinate to get the elevation data for
+     * @param latLon the coordinate to get the elevation data for
      * @return the elevation value or <code>Double.NaN</code>, if no value is present
      */
-    public static double readElevation(ILatLon coor) {
-        String tag = getHgtFileName(coor);
-        return readElevation(coor, tag);
-    }
+    private double readElevationFromCache(ILatLon latLon) {
+        String hgtPrefix = getHgtPrefix(latLon);
+        HgtCacheData hgtCacheData;
 
-    /**
-     * Reads the elevation value for the given coordinate.
-     *
-     * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>
-     * @param coor the coordinate to get the elevation data for
-     * @param fileName The expected filename
-     * @return the elevation value or <code>Double.NaN</code>, if no value is present
-     */
-    public static double readElevation(ILatLon coor, String fileName) {
+        synchronized(cache) {
+            hgtCacheData = cache.get(hgtPrefix);
+        }
 
-        short[][] sb = cache.get(fileName);
+        if (hgtCacheData == null || hgtCacheData.getStatus() != HgtCacheData.Status.VALID)
+            return ElevationHelper.NO_ELEVATION;
 
-        if (sb == null) {
+        short[][] data = hgtCacheData.getData();
+        if (data == null)
             return ElevationHelper.NO_ELEVATION;
-        }
 
-        int[] index = getIndex(coor, sb.length);
-        short ele = sb[index[0]][index[1]];
+        int[] index = getIndex(latLon, data.length);
+        short ele;
 
-        if (ele == HGT_VOID) {
+        try {
+            ele = data[index[0]][index[1]];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Logging.error("Elevation: Error reading elevation data for prefix " + hgtPrefix + ":" + e.toString());
             return ElevationHelper.NO_ELEVATION;
         }
+
+        if (ele == HGT_VOID)
+            return ElevationHelper.NO_ELEVATION;
         return ele;
     }
 
-    public static Optional<Bounds> getBounds(ILatLon location) {
-        final String fileName = getHgtFileName(location);
-        final short[][] sb = cache.get(fileName);
+    public Optional<Bounds> getBounds(ILatLon location) {
+        String hgtPrefix = getHgtPrefix(location);
+        short[][] data = null;
+
+        synchronized(cache) {
+            data = cache.get(hgtPrefix).getData();
+        }
 
-        if (sb == null) {
+        if (data == null) {
             return Optional.empty();
         }
 
         final double latDegrees = location.lat();
         final double lonDegrees = location.lon();
 
-        final float fraction = ((float) SRTM_EXTENT) / sb.length;
+        final float fraction = ((float) SRTM_EXTENT) / data.length;
         final int latitude = (int) Math.floor(latDegrees) + (latDegrees < 0 ? 1 : 0);
         final int longitude = (int) Math.floor(lonDegrees) + (lonDegrees < 0 ? 1 : 0);
 
-        final int[] index = getIndex(location, sb.length);
+        final int[] index = getIndex(location, data.length);
         final int latSign = latitude > 0 ? 1 : -1;
         final int lonSign = longitude > 0 ? 1 : -1;
         final double minLat = latitude + latSign * fraction * index[0];
@@ -217,6 +324,26 @@ public class HgtReader {
         return new int[] { latitude, longitude };
     }
 
+    /**
+     * Returns the HGT file, which contains elevation data for a given location, found in the given HGT directory.
+     * Note: This method only checks if there is a file name with appropriate name. It does not check if the file content is appropriate.
+     * @param latLon The location for which the SRTM3 HGT file with the elevation data is of interest.
+     * @return The HGT file with the elevation data for the given location or <code>null</code>
+     *         if there is no file with an appropriate name.
+     */
+    public File getHgtFile(ILatLon latLon) {
+        String hgtPrefixFromLatLon = getHgtPrefix(latLon);
+        // https://www.baeldung.com/java-list-directory-files
+        Set<File> files = Stream.of(hgtDirectory.listFiles()).filter(file -> !file.isDirectory()).collect(Collectors.toSet());
+        for (File file : files) {
+            String hgtPrefixFromFile = getHgtPrefix(file.getName());
+            if (hgtPrefixFromFile != null && hgtPrefixFromFile.equals(hgtPrefixFromLatLon))
+                return file;
+        }
+
+        return null;
+    }
+
     /**
      * Gets the associated HGT file name for the given way point. Usually the
      * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude
@@ -225,7 +352,11 @@ public class HgtReader {
      * @param latLon the coordinate to get the filename for
      * @return the file name of the HGT file
      */
-    public static String getHgtFileName(ILatLon latLon) {
+    /*public static String getHgtFileName(ILatLon latLon) {
+        return getHgtPrefix(latLon) + HGT_EXT;
+    }*/
+
+    public static String getHgtPrefix(ILatLon latLon) {
         int lat = (int) Math.floor(latLon.lat());
         int lon = (int) Math.floor(latLon.lon());
 
@@ -241,7 +372,14 @@ public class HgtReader {
             lon = Math.abs(lon);
         }
 
-        return String.format("%s%2d%s%03d" + HGT_EXT, latPref, lat, lonPref, lon);
+        return String.format("%s%2d%s%03d", latPref, lat, lonPref, lon);
+    }
+
+    public static String getHgtPrefix(String fileName) {
+        Matcher matcher = HGT_PREFIX_PATTERN.matcher(fileName);
+        if (matcher.matches())
+            return matcher.group(1);
+        return null;
     }
 
     public static double frac(double d) {
@@ -254,7 +392,125 @@ public class HgtReader {
         return fPart;
     }
 
-    public static void clearCache() {
-        cache.clear();
+    public void clearCache() {
+        synchronized(cache) {
+            cache.clear();
+        }
+    }
+
+    @Override
+    public void hgtFileDownloading(ILatLon latLon) {
+        String hgtPrefix = getHgtPrefix(latLon);
+
+        synchronized (cache) {
+            HgtCacheData hgtCacheData = cache.get(hgtPrefix);
+            // Should not happen
+            if (hgtCacheData == null) {
+                hgtCacheData = new HgtCacheData();
+                cache.put(hgtPrefix, hgtCacheData);
+            }
+            hgtCacheData.setDownloading();
+        }
+    }
+
+    @Override
+    public void hgtFileDownloadSucceeded(ILatLon latLon, File hgtFile) {
+        short[][] data = null;
+        try {
+            data = readHgtFile(hgtFile.getAbsolutePath());
+        } catch (Exception e) {
+            Logging.error("Elevation: Error reading HGT file " + hgtFile.getAbsolutePath() + ": " + e.toString());
+        }
+
+        String hgtPrefix = getHgtPrefix(latLon);
+        HgtCacheData hgtCacheData;
+        synchronized (cache) {
+            hgtCacheData = cache.get(hgtPrefix);
+            if (hgtCacheData != null)
+                hgtCacheData.setData(data);
+            // Should not happen
+            else {
+                hgtCacheData = new HgtCacheData(data);
+                cache.put(hgtPrefix, hgtCacheData);
+            }
+        }
+
+        // In case that the downloaded file is corrupt, try to delete it
+        if (data == null) {
+            hgtCacheData.setDownloadFailed();
+            Logging.info("Elevation: Deleting downloaded, but corrupt HGT file: " + hgtFile.getAbsolutePath());
+            try {
+                Files.delete(Paths.get(hgtFile.getAbsolutePath()));
+            } catch (IOException e) {
+                Logging.error("Elevation: Error deleting downloaded, but corrupt HGT file: " + e.toString());
+            }
+        }
+    }
+
+    @Override
+    public void hgtFileDownloadFailed(ILatLon latLon) {
+        String hgtPrefix = getHgtPrefix(latLon);
+
+        synchronized (cache) {
+            HgtCacheData hgtCacheData = cache.get(hgtPrefix);
+            // Should not happen
+            if (hgtCacheData == null) {
+                hgtCacheData = new HgtCacheData();
+                cache.put(hgtPrefix, hgtCacheData);
+            }
+            hgtCacheData.setDownloadFailed();
+        }
+
+    }
+
+    private static class HgtCacheData {
+
+        private short[][] data;
+        private Status status;
+
+        enum Status {
+            VALID,
+            MISSING,
+            DOWNLOAD_SCHEDULED,
+            DOWNLOADING,
+            DOWNLOAD_FAILED
+        }
+
+        public HgtCacheData() {
+            this(null);
+        }
+
+        public HgtCacheData(short[][] data) {
+            setData(data);
+        }
+
+        public short[][] getData() {
+            return data;
+        }
+
+        public Status getStatus() {
+            return status;
+        }
+
+        public void setDownloadScheduled() {
+            status = Status.DOWNLOAD_SCHEDULED;
+        }
+
+        public void setDownloading() {
+            status = Status.DOWNLOADING;
+        }
+
+        public void setDownloadFailed() {
+            status = Status.DOWNLOAD_FAILED;
+            data = null;
+        }
+
+        public void setData(short[][] data) {
+            if (data == null)
+                status = Status.MISSING;
+            else
+                status = Status.VALID;
+            this.data = data;
+        }
     }
 }
diff --git a/src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridLayer.java b/src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridLayer.java
index da76bf4..68c3393 100644
--- a/src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridLayer.java
+++ b/src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridLayer.java
@@ -34,7 +34,6 @@ import org.openstreetmap.josm.gui.Notification;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.plugins.elevation.ElevationHelper;
-import org.openstreetmap.josm.plugins.elevation.HgtReader;
 import org.openstreetmap.josm.plugins.elevation.IVertexRenderer;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Logging;
@@ -58,7 +57,6 @@ public class ElevationGridLayer extends Layer implements TileLoaderListener, Mou
 
     public ElevationGridLayer(String name) {
         super(name);
-        HgtReader.clearCache();
         MainApplication.getMap().mapView.addMouseListener(this);
 
         setOpacity(0.8);
@@ -283,9 +281,8 @@ public class ElevationGridLayer extends Layer implements TileLoaderListener, Mou
     }
 
     @Override
-    public void destroy() {
-        super.destroy();
-        HgtReader.clearCache();
+    public void finalize() throws Throwable {
+        super.finalize();
         MainApplication.getMap().mapView.removeMouseListener(this);
     }
 }
diff --git a/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreference.java b/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreference.java
new file mode 100644
index 0000000..fac1f86
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreference.java
@@ -0,0 +1,54 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.elevation.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import javax.swing.Box;
+
+import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
+import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
+import org.openstreetmap.josm.plugins.elevation.ElevationPreferences;
+import org.openstreetmap.josm.plugins.elevation.ElevationProfilePlugin;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Elevation data sub-preferences in preferences.
+ * @author Harald Hetzner
+ *
+ */
+public final class ElevationPreference extends DefaultTabPreferenceSetting {
+
+    private ElevationPreferencesPanel pnlHgtPreferences;
+
+    public ElevationPreference() {
+        super("elevation", tr("Elevation Data"), tr("Elevation preferences and connection settings for the HGT server."));
+    }
+
+    @Override
+    public void addGui(PreferenceTabbedPane gui) {
+        pnlHgtPreferences = new ElevationPreferencesPanel();
+        pnlHgtPreferences.add(Box.createVerticalGlue(), GBC.eol().fill());
+        gui.createPreferenceTab(this).add(pnlHgtPreferences, GBC.eol().fill());
+    }
+
+    /**
+     * Saves the values to the preferences and applies them.
+     */
+    @Override
+    public boolean ok() {
+        // Save to preferences file
+        pnlHgtPreferences.saveToPreferences();
+
+        // Apply preferences
+        boolean elevationEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED);
+        ElevationProfilePlugin.getInstance().setElevationEnabled(elevationEnabled);
+
+        return false;
+    }
+
+    @Override
+    public String getHelpContext() {
+        return null;
+    }
+}
diff --git a/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreferencesPanel.java b/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreferencesPanel.java
new file mode 100644
index 0000000..eb865a0
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreferencesPanel.java
@@ -0,0 +1,188 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.elevation.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.io.IOException;
+import java.net.URI;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.event.HyperlinkEvent;
+
+import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
+import org.openstreetmap.josm.gui.widgets.JosmTextField;
+import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
+import org.openstreetmap.josm.plugins.elevation.ElevationPreferences;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.IPreferences;
+import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Component allowing input of HGT (elevation) server settings.
+ */
+public class ElevationPreferencesPanel extends VerticallyScrollablePanel {
+
+    static final class AutoSizePanel extends JPanel {
+        AutoSizePanel() {
+            super(new GridBagLayout());
+        }
+
+        @Override
+        public Dimension getMinimumSize() {
+            return getPreferredSize();
+        }
+    }
+
+    private final JCheckBox cbEnableElevation = new JCheckBox(tr("Enable Use of Elevation Data"));
+    private final JMultilineLabel lblpElevationData =
+            new JMultilineLabel(tr("<html>STRM3 HGT files can be downloaded from <a href=\"{0}\">{0}</a>.</html>", ElevationPreferences.HGT_SERVER_BASE_URL));
+    private final JCheckBox cbEnableElevationProfile = new JCheckBox(tr("Enable Elevation Profile Layer"));
+    private final JCheckBox cbEnableAutoDownload = new JCheckBox(tr("Enable Automatic Downloading of Elevation Data"));
+    private final JLabel lblAuthBearer = new JLabel(tr("Authorization Bearer Token:"));
+    private final JosmTextField tfAuthBearer = new JosmTextField();
+    private final JMultilineLabel lblAuthBearerNotes =
+            new JMultilineLabel(tr("<html>You need to register at <a href=\"{0}\">{0}</a> to create the authorization bearer token.</html>",
+                    ElevationPreferences.HGT_SERVER_REGISTRATION_URL));
+
+    /**
+     * Builds the panel for the elevation preferences.
+     *
+     * @return Panel with elevation preferences
+     */
+    protected final JPanel buildHgtPreferencesPanel() {
+        cbEnableElevation.setToolTipText(tr("STRM3 HGT files need to be placed in {0}", ElevationPreferences.DEFAULT_HGT_DIRECTORY.getAbsolutePath()));
+        cbEnableElevation.addItemListener(event -> updateEnabledState());
+
+        lblpElevationData.setEditable(false);
+        lblpElevationData.addHyperlinkListener(event -> browseHyperlink(event));
+
+        cbEnableElevationProfile.addItemListener(event -> updateEnabledState());
+
+        cbEnableAutoDownload.setToolTipText(tr("STRM3 HGT files will be downloaded from {0}", ElevationPreferences.HGT_SERVER_BASE_URL));
+        cbEnableAutoDownload.addItemListener(event -> updateEnabledState());
+
+        lblAuthBearerNotes.setEditable(false);
+        lblAuthBearerNotes.addHyperlinkListener(event -> browseHyperlink(event));
+
+        JPanel pnl = new AutoSizePanel();
+        GridBagConstraints gc = new GridBagConstraints();
+
+        gc.anchor = GridBagConstraints.LINE_START;
+        gc.insets = new Insets(5, 5, 0, 0);
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.gridwidth = 2;
+        gc.weightx = 1.0;
+        pnl.add(cbEnableElevation, gc);
+
+        gc.gridy = 1;
+        pnl.add(lblpElevationData, gc);
+
+        gc.gridy = 2;
+        pnl.add(cbEnableElevationProfile, gc);
+
+        gc.gridy = 3;
+        pnl.add(cbEnableAutoDownload, gc);
+
+        gc.gridy = 4;
+        gc.fill = GridBagConstraints.NONE;
+        gc.gridwidth = 1;
+        gc.weightx = 0.0;
+        pnl.add(lblAuthBearer, gc);
+
+        gc.gridx = 1;
+        gc.fill = GridBagConstraints.HORIZONTAL;
+        gc.weightx = 1.0;
+        pnl.add(tfAuthBearer, gc);
+
+        gc.gridy = 5;
+        gc.gridx = 0;
+        gc.gridwidth = 2;
+        gc.weightx = 1.0;
+        pnl.add(lblAuthBearerNotes, gc);
+
+        // add an extra spacer, otherwise the layout is broken
+        gc.gridy = 6;
+        gc.gridwidth = 2;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weighty = 1.0;
+        pnl.add(new JPanel(), gc);
+        return pnl;
+    }
+
+
+    /**
+     * Initializes the panel with the values from the preferences.
+     */
+    public final void initFromPreferences() {
+        IPreferences pref = Config.getPref();
+
+        cbEnableElevation.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED));
+        cbEnableElevationProfile.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_PROFILE_ENABLED));
+        cbEnableAutoDownload.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED));
+        tfAuthBearer.setText(pref.get(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, ElevationPreferences.DEFAULT_ELEVATION_SERVER_AUTH_BEARER));
+    }
+
+    private final void updateEnabledState() {
+        if (cbEnableElevation.isSelected()) {
+            lblpElevationData.setEnabled(true);
+            cbEnableElevationProfile.setEnabled(true);
+            cbEnableAutoDownload.setEnabled(true);
+            lblAuthBearer.setEnabled(cbEnableAutoDownload.isSelected());
+            tfAuthBearer.setEnabled(cbEnableAutoDownload.isSelected());
+            lblAuthBearerNotes.setEnabled(cbEnableAutoDownload.isSelected());
+        }
+        else {
+            lblpElevationData.setEnabled(false);
+            cbEnableElevationProfile.setEnabled(false);
+            cbEnableAutoDownload.setEnabled(false);
+            lblAuthBearer.setEnabled(false);
+            tfAuthBearer.setEnabled(false);
+            lblAuthBearerNotes.setEnabled(false);
+        }
+    }
+
+    // https://stackoverflow.com/questions/14101000/hyperlink-to-open-in-browser-in-java
+    // https://www.codejava.net/java-se/swing/how-to-create-hyperlink-with-jlabel-in-java-swing
+    private final void browseHyperlink(HyperlinkEvent event) {
+            if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+                String url = event.getURL().toString();
+                try {
+                    Desktop.getDesktop().browse(URI.create(url));
+                } catch (IOException e) {
+                    Logging.error(e.toString());
+                }
+            }
+    }
+
+    /**
+     * Constructs a new {@code HgtPreferencesPanel}.
+     */
+    public ElevationPreferencesPanel() {
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+        add(buildHgtPreferencesPanel(), GBC.eop().anchor(GridBagConstraints.NORTHWEST).fill(GridBagConstraints.BOTH));
+
+        initFromPreferences();
+        updateEnabledState();
+    }
+
+    /**
+     * Saves the current values to the preferences
+     */
+    public void saveToPreferences() {
+        IPreferences pref = Config.getPref();
+        pref.putBoolean(ElevationPreferences.ELEVATION_ENABLED, cbEnableElevation.isSelected());
+        pref.putBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED, cbEnableElevationProfile.isSelected());
+        pref.putBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED, cbEnableAutoDownload.isSelected());
+        pref.put(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, tfAuthBearer.getText());
+    }
+}
diff --git a/src/org/openstreetmap/josm/plugins/elevation/gui/LocalElevationLabel.java b/src/org/openstreetmap/josm/plugins/elevation/gui/LocalElevationLabel.java
new file mode 100644
index 0000000..6c56016
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/elevation/gui/LocalElevationLabel.java
@@ -0,0 +1,63 @@
+package org.openstreetmap.josm.plugins.elevation.gui;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.text.DecimalFormat;
+
+import org.openstreetmap.josm.data.coor.ILatLon;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapStatus;
+import org.openstreetmap.josm.gui.widgets.ImageLabel;
+import org.openstreetmap.josm.plugins.elevation.ElevationHelper;
+import org.openstreetmap.josm.plugins.elevation.HgtReader;
+import org.openstreetmap.josm.tools.GBC;
+
+public class LocalElevationLabel extends ImageLabel implements MouseMotionListener {
+
+	private final MapFrame mapFrame;
+
+	private final DecimalFormat ELEVATION_FORMAT = new DecimalFormat("0 m");
+
+    public LocalElevationLabel(MapFrame mapFrame) {
+        super("ele", tr("The terrain elevation at the mouse pointer."), 10, MapStatus.PROP_BACKGROUND_COLOR.get());
+
+        mapFrame.mapView.addMouseMotionListener(this);
+
+        setForeground(MapStatus.PROP_FOREGROUND_COLOR.get());
+        // Add after the longitude ImageLabel at index = 2
+        // or at index 0 or 1, if index = 2 should be out of range
+        int index = Math.min(mapFrame.statusLine.getComponentCount(), 2);
+        mapFrame.statusLine.add(this, GBC.std().insets(3, 0, 0, 0), index);
+
+        this.mapFrame = mapFrame;
+    }
+
+    private void updateEleText(ILatLon coor) {
+        double ele = HgtReader.getInstance().getElevationFromHgt(coor);
+        if (ElevationHelper.isValidElevation(ele))
+            setText(ELEVATION_FORMAT.format(ele));
+        else
+            setText(tr("No data"));
+    }
+
+	public void destroy() {
+		mapFrame.mapView.removeMouseMotionListener(this);
+	}
+
+	@Override
+	public void mouseDragged(MouseEvent e) {
+		mouseMoved(e);
+	}
+
+	@Override
+	public void mouseMoved(MouseEvent e) {
+		if (mapFrame.mapView.getCenter() == null)
+            return;
+        // Do not update the view if ctrl or right button is pressed.
+        if ((e.getModifiersEx() & (MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0)
+            updateEleText(mapFrame.mapView.getLatLon(e.getX(), e.getY()));
+
+	}
+}
