Ticket #22596: JOSM_ElevationProfile_18494.diff

File JOSM_ElevationProfile_18494.diff, 58.5 KB (added by hhtznr, 16 months ago)

Git diff of reworked elevation patch against ElevationProfile

  • build.xml

    diff --git a/build.xml b/build.xml
    index 572c9df..c74d3ef 100644
    a b  
    88    <!-- Configure these properties (replace "..." accordingly).
    99         See https://josm.openstreetmap.de/wiki/DevelopersGuide/DevelopingPlugins
    1010    -->
    11     <property name="plugin.author" value="Oliver Wieland"/>
     11    <property name="plugin.author" value="Oliver Wieland, Harald Hetzner"/>
    1212    <property name="plugin.class" value="org.openstreetmap.josm.plugins.elevation.ElevationProfilePlugin"/>
    13     <property name="plugin.description" value="Shows the elevation profile and some statistical data of a GPX track."/>
     13    <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."/>
    1414    <property name="plugin.icon" value="images/elevation.png"/>
    1515    <property name="plugin.link" value="https://wiki.openstreetmap.org/wiki/JOSM/Plugins/ElevationProfile"/>
    1616
  • new file images/preferences/elevation.svg

    diff --git a/images/preferences/elevation.svg b/images/preferences/elevation.svg
    new file mode 100644
    index 0000000..4115718
    - +  
     1<?xml version="1.0" encoding="UTF-8"?>
     2<svg
     3   width="24"
     4   height="24"
     5   viewBox="0 0 24 24">
     6  <g
     7     transform="translate(0,-1037.3622)">
     8    <path
     9       style="fill:#89a02c;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     10       d="M 23,1055.3622 H 1 l 5,-7 5,4 6,-10 6,10 z" />
     11    <path
     12       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     13       d="m 1,1055.3622 5,-7 5,4 6,-10 6,10" />
     14  </g>
     15</svg>
  • new file images/statusline/ele.svg

    diff --git a/images/statusline/ele.svg b/images/statusline/ele.svg
    new file mode 100644
    index 0000000..969f2d2
    - +  
     1<?xml version="1.0" encoding="UTF-8"?>
     2<svg
     3   version="1.1"
     4   width="18px"
     5   height="18px"
     6   viewBox="0 0 18 18"
     7   fill="none" >
     8  <path
     9     d="M 9,1 V 17"
     10     stroke="#ee4422"
     11     stroke-width="1.88562" />
     12  <path
     13     style="fill:none;stroke:#000000;stroke-width:0.93133px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     14     d="M 0.99621412,16.121325 9,2.3402182 17.003786,16.121325 Z" />
     15</svg>
  • src/org/openstreetmap/josm/plugins/elevation/ElevationHelper.java

    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 b public final class ElevationHelper {  
    168168        if (ll != null) {
    169169            // Try to read data from SRTM file
    170170            // TODO: Option to switch this off
    171             double eleHgt = HgtReader.getElevationFromHgt(ll);
     171            double eleHgt = HgtReader.getInstance().getElevationFromHgt(ll);
    172172
    173173            if (isValidElevation(eleHgt)) {
    174174                return eleHgt;
    public final class ElevationHelper {  
    184184     */
    185185    public static Optional<Bounds> getBounds(ILatLon location) {
    186186        if (location != null) {
    187             return HgtReader.getBounds(location);
     187            return HgtReader.getInstance().getBounds(location);
    188188        }
    189189        return Optional.empty();
    190190    }
  • new file src/org/openstreetmap/josm/plugins/elevation/ElevationPreferences.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.plugins.elevation;
     3
     4import java.io.File;
     5import java.nio.file.Paths;
     6
     7import org.openstreetmap.josm.data.Preferences;
     8
     9/**
     10 * Property keys and default values for elevation data preferences.
     11 *
     12 * @author Harald Hetzner
     13 *
     14 */
     15public class ElevationPreferences {
     16
     17    /** Property key for enabling or disabling use of elevation data. */
     18    public static final String ELEVATION_ENABLED = "elevation.enabled";
     19
     20    /** Property key for enabling or disabling elevation profile layer. */
     21    public static final String ELEVATION_PROFILE_ENABLED = "elevation.profile.enabled";
     22
     23    /** Property key for enabling or disabling automatic download of elevation data. */
     24    public static final String ELEVATION_AUTO_DOWNLOAD_ENABLED = "elevation.autodownload";
     25
     26    /**
     27     * Property key for authentication bearer token for SRTM HGT server.
     28     * @see HgtDownloader
     29     */
     30    public static final String ELEVATION_SERVER_AUTH_BEARER = "elevation.hgt.server.auth.bearer";
     31
     32    /** Default property value for enabling use of elevation data: {@code false}. */
     33    public static final boolean DEFAULT_ELEVATION_ENABLED = false;
     34
     35    /** Default property value for enabling the elevation profile layer: {@code false}. */
     36    public static final boolean DEFAULT_ELEVATION_PROFILE_ENABLED = false;
     37
     38    /** Default property value for enabling automatic download of elevation data: {@code false}. */
     39    public static final boolean DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED = false;
     40
     41    /** Default property value for authentication bearer token for SRTM HGT server: Empty {@code String}. */
     42    public static final String DEFAULT_ELEVATION_SERVER_AUTH_BEARER = "";
     43
     44    /** Default path, where SRTM3 HGT files need to be placed, respectively to which they will be downloaded. */
     45    public static final File DEFAULT_HGT_DIRECTORY = Paths.get(Preferences.main().getDirs().getCacheDirectory(true).toString(), "elevation", "SRTM3").toFile();
     46
     47    /**
     48     * URL of <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
     49     * where users need to register and create an authorization bearer token in order to download elevation
     50     * data from {@link ElevationPreferences#HGT_SERVER_BASE_URL}.
     51     */
     52    public static final String HGT_SERVER_REGISTRATION_URL = "https://urs.earthdata.nasa.gov/users/new/";
     53
     54    /**
     55     * URL of
     56     * <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>
     57     * where SRTM3 HGT files can be downloaded.
     58     *
     59     * Requires registration at {@link ElevationPreferences#HGT_SERVER_REGISTRATION_URL}.
     60     */
     61    public static final String HGT_SERVER_BASE_URL = "https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/";
     62
     63    /**
     64     * Prefix of compressed as-downloaded HGT files.
     65     */
     66    public static final String SRTM3_HGT_ZIP_FILE_PREFIX = ".SRTMGL3.hgt.zip";
     67
     68    private ElevationPreferences() {}
     69}
  • src/org/openstreetmap/josm/plugins/elevation/ElevationProfilePlugin.java

    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 b import org.openstreetmap.josm.gui.IconToggleButton;  
    1010import org.openstreetmap.josm.gui.MainApplication;
    1111import org.openstreetmap.josm.gui.MainMenu;
    1212import org.openstreetmap.josm.gui.MapFrame;
     13import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    1314import org.openstreetmap.josm.plugins.Plugin;
    1415import org.openstreetmap.josm.plugins.PluginInformation;
    1516import org.openstreetmap.josm.plugins.elevation.actions.AddElevationLayerAction;
     17import org.openstreetmap.josm.plugins.elevation.gui.ElevationPreference;
    1618import org.openstreetmap.josm.plugins.elevation.gui.ElevationProfileDialog;
    1719import org.openstreetmap.josm.plugins.elevation.gui.ElevationProfileLayer;
     20import org.openstreetmap.josm.plugins.elevation.gui.LocalElevationLabel;
     21import org.openstreetmap.josm.spi.preferences.Config;
    1822
    1923/**
    2024 * Plugin class for displaying an elevation profile of the tracks.
    2125 * @author Oliver Wieland &lt;oliver.wieland@online.de&gt;
     26 * @author Harald Hetzner
    2227 *
    2328 */
    2429public class ElevationProfilePlugin extends Plugin {
    2530
    2631    private static ElevationProfileLayer currentLayer;
    2732
     33    private ElevationPreference preference = null;
     34
     35    private boolean elevationEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED);
     36
     37    private LocalElevationLabel localElevationLabel = null;
     38
     39    private static ElevationProfilePlugin instance = null;
     40
     41    public static ElevationProfilePlugin getInstance() {
     42        return instance;
     43    }
     44
    2845    /**
    2946     * Initializes the plugin.
    3047     * @param info Context information about the plugin.
    public class ElevationProfilePlugin extends Plugin {  
    3754
    3855        // TODO: Disable this view as long as it is not stable
    3956        MainMenu.add(MainApplication.getMenu().imagerySubMenu, new AddElevationLayerAction(), false, 0);
     57
     58        instance = this;
    4059    }
    4160
    4261    /**
    public class ElevationProfilePlugin extends Plugin {  
    4766    @Override
    4867    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
    4968        super.mapFrameInitialized(oldFrame, newFrame);
     69        setElevationEnabled(elevationEnabled, newFrame);
     70    }
    5071
    51         if (newFrame != null) {
    52             ElevationMapMode eleMode = new ElevationMapMode("Elevation profile");
    53             newFrame.addMapMode(new IconToggleButton(eleMode));
    54             ElevationProfileDialog eleProfileDlg = new ElevationProfileDialog();
    55             eleProfileDlg.addModelListener(eleMode);
    56             eleProfileDlg.setProfileLayer(getCurrentLayer());
    57             newFrame.addToggleDialog(eleProfileDlg);
    58         }
     72    /**
     73     * Called in the preferences dialog to create a preferences page for the plugin,
     74     * if any available.
     75     * @return the preferences dialog, or {@code null}
     76     */
     77    @Override
     78    public PreferenceSetting getPreferenceSetting() {
     79        if (preference == null)
     80            preference = new ElevationPreference();
     81        return preference;
    5982    }
    6083
    6184    /**
    public class ElevationProfilePlugin extends Plugin {  
    124147        }
    125148                );
    126149    }
     150
     151    public boolean isElevationEnabled() {
     152        return elevationEnabled;
     153    }
     154
     155    /**
     156     * Enable or disable displaying elevation at the position of the mouse pointer.
     157     * @param enabled If {@code true} displaying of elevation is enabled, else disabled.
     158     * @see HgtReader
     159     */
     160    public void setElevationEnabled(boolean enabled) {
     161        setElevationEnabled(enabled, MainApplication.getMap());
     162    }
     163
     164    private void setElevationEnabled(boolean enabled, MapFrame mapFrame) {
     165        if (localElevationLabel == null &&  mapFrame != null) {
     166            localElevationLabel = new LocalElevationLabel(mapFrame);
     167            localElevationLabel.setVisible(enabled);
     168        }
     169        if (enabled) {
     170            // Elevation profile
     171            boolean elevationProfileEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED,
     172                    ElevationPreferences.DEFAULT_ELEVATION_PROFILE_ENABLED);
     173            if (elevationProfileEnabled) {
     174                ElevationMapMode eleMode = new ElevationMapMode("Elevation profile");
     175                mapFrame.addMapMode(new IconToggleButton(eleMode));
     176                ElevationProfileDialog eleProfileDlg = new ElevationProfileDialog();
     177                eleProfileDlg.addModelListener(eleMode);
     178                eleProfileDlg.setProfileLayer(getCurrentLayer());
     179                mapFrame.addToggleDialog(eleProfileDlg);
     180            }
     181
     182            // Auto-download of HGT files
     183            boolean elevationAutoDownloadEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED,
     184                    ElevationPreferences.DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED);
     185            // If enabled, HgtDownloader used by HgtReader will by itself read the authentication bearer token from the preferences
     186            HgtReader.getInstance().setAutoDownloadEnabled(elevationAutoDownloadEnabled);
     187        }
     188        else {
     189            if (localElevationLabel != null) {
     190                localElevationLabel.destroy();
     191                localElevationLabel = null;
     192            }
     193            HgtReader.destroyInstance();
     194        }
     195        elevationEnabled = enabled;
     196    }
     197
    127198}
  • new file src/org/openstreetmap/josm/plugins/elevation/HgtDownloadListener.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.plugins.elevation;
     3
     4import java.io.File;
     5
     6import org.openstreetmap.josm.data.coor.ILatLon;
     7
     8/**
     9 *
     10 * @author Harald Hetzner
     11 *
     12 */
     13public interface HgtDownloadListener {
     14
     15    /**
     16     * Informs the implementing class that downloading of HGT data for the given
     17     * coordinates has started.
     18     *
     19     * To be called by the thread downloading as soon as downloading actually started.
     20     *
     21     * @param latLon The coordinates for which the HGT data is now being downloaded.
     22     */
     23    public void hgtFileDownloading(ILatLon latLon);
     24
     25    /**
     26     * Informs the implementing class that HGT data for the given coordinates was
     27     * successfully downloaded.
     28     *
     29     * To be called by the thread downloading as soon as the download finished.
     30     *
     31     * @param latLon The coordinates for which the download of HGT data succeeded.
     32     * @param hgtFile The downloaded HGT file.
     33     */
     34    public void hgtFileDownloadSucceeded(ILatLon latLon, File hgtFile);
     35
     36    /**
     37     * Informs the implementing class that downloading HGT data for the given
     38     * coordinates failed.
     39     *
     40     * To be called by the thread downloading as soon as downloading fails.
     41     *
     42     * @param latLon The coordinates for which the download of HGT data failed.
     43     */
     44    public void hgtFileDownloadFailed(ILatLon latLon);
     45
     46}
  • new file src/org/openstreetmap/josm/plugins/elevation/HgtDownloader.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.plugins.elevation;
     3
     4import java.io.File;
     5import java.io.IOException;
     6import java.io.InputStream;
     7import java.net.MalformedURLException;
     8import java.net.URL;
     9import java.nio.file.Files;
     10import java.nio.file.Path;
     11import java.nio.file.Paths;
     12import java.nio.file.StandardCopyOption;
     13import java.util.LinkedList;
     14import java.util.concurrent.ExecutorService;
     15import java.util.concurrent.Executors;
     16
     17import org.openstreetmap.josm.data.coor.ILatLon;
     18import org.openstreetmap.josm.gui.io.DownloadFileTask;
     19import org.openstreetmap.josm.spi.preferences.Config;
     20import org.openstreetmap.josm.tools.HttpClient;
     21import org.openstreetmap.josm.tools.Logging;
     22
     23/**
     24 * Class {@code HgtDownloader} downloads SRTM HGT (Shuttle Radar Topography Mission Height) files with elevation data.
     25 * Currently this class is restricted to a resolution of 3 arc seconds (SRTM3).
     26 *
     27 * SRTM3 HGT files are available at
     28 * <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>.
     29 *
     30 * In order to access these files, registration at <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
     31 * and creating an authorization bearer token on this site are required.
     32 *
     33 * @author Harald Hetzner
     34 * @see HgtReader
     35 *
     36 */
     37public class HgtDownloader {
     38
     39    private final URL baseUrl;
     40    private File hgtDirectory;
     41
     42    private String authHeader;
     43
     44    private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
     45
     46    private final LinkedList<HgtDownloadListener> downloadListeners = new LinkedList<HgtDownloadListener>();
     47
     48    private boolean uncompressDownloadedFiles = false;
     49
     50    public HgtDownloader(File hgtDirectory, String url, String bearer) throws MalformedURLException {
     51        // May throw MalformedURLException
     52        this.baseUrl = new URL(url);
     53        // https://stackoverflow.com/questions/38085964/authorization-bearer-token-in-httpclient
     54        if (!bearer.equals(""))
     55            authHeader = "Bearer " + bearer;
     56        else
     57            authHeader = null;
     58        this.hgtDirectory = hgtDirectory;
     59    }
     60
     61    public HgtDownloader(File hgtDirectory) throws MalformedURLException {
     62        this(hgtDirectory, ElevationPreferences.HGT_SERVER_BASE_URL,
     63                Config.getPref().get(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, ElevationPreferences.DEFAULT_ELEVATION_SERVER_AUTH_BEARER));
     64    }
     65
     66    public void setHgtDirectory(File hgtDirectory) {
     67        this.hgtDirectory = hgtDirectory;
     68    }
     69
     70    public void setUncompressDownloadedFiles(boolean b) {
     71        uncompressDownloadedFiles = b;
     72    }
     73
     74    public void addDownloadListener(HgtDownloadListener listener) {
     75        if (!downloadListeners.contains(listener))
     76            downloadListeners.add(listener);
     77    }
     78
     79    public void downloadHgtFile(ILatLon latLon) {
     80        EXECUTOR.submit(new DownloadHgtFileTask(latLon));
     81    }
     82
     83    /**
     84     * Gets the associated HGT file name for the given coordinate. Usually the
     85     * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude
     86     * without decimals and <i>mmm</i> is the longitude.
     87     *
     88     * @param latLon The coordinate to get the filename for
     89     * @return The file name of the HGT file.
     90     */
     91    public static String getSrtm3HgtZipFileName(ILatLon latLon) {
     92        return HgtReader.getHgtPrefix(latLon) + ElevationPreferences.SRTM3_HGT_ZIP_FILE_PREFIX;
     93    }
     94
     95    private class DownloadHgtFileTask implements Runnable {
     96
     97        private final ILatLon latLon;
     98
     99
     100        public DownloadHgtFileTask(ILatLon latLon) {
     101            this.latLon = latLon;
     102        }
     103
     104        @Override
     105        public void run() {
     106            downloading();
     107            String hgtDirectoryPath = HgtDownloader.this.hgtDirectory.toString();
     108            String hgtZipFileName = HgtDownloader.getSrtm3HgtZipFileName(latLon);
     109            File hgtFile = null;
     110
     111            URL url = null;
     112            try {
     113                url = new URL(HgtDownloader.this.baseUrl + hgtZipFileName);
     114            } catch (MalformedURLException e) {
     115                downloadFailed();
     116                return;
     117            }
     118            HttpClient httpClient = HttpClient.create(url);
     119            if (authHeader != null)
     120                httpClient.setHeader("Authorization", authHeader);
     121            HttpClient.Response response = null;
     122            try {
     123                response = httpClient.connect();
     124                //Logging.info("Elevation: HGT server responded: " + response.getResponseCode() + " " +  response.getResponseMessage());
     125                // https://urs.earthdata.nasa.gov/documentation/for_users/data_access/java
     126                if (response.getResponseCode() != 200) {
     127                    downloadFailed();
     128                    return;
     129                }
     130                InputStream in = response.getContent();
     131                Path downloadedZipFile = Paths.get(hgtDirectoryPath, hgtZipFileName);
     132                Files.copy(in, downloadedZipFile, StandardCopyOption.REPLACE_EXISTING);
     133                if (uncompressDownloadedFiles) {
     134                    DownloadFileTask.unzipFileRecursively(downloadedZipFile.toFile(), hgtDirectoryPath);
     135                    Files.delete(downloadedZipFile);
     136                    // Determine the file, which was uncompressed
     137                    hgtFile = HgtReader.getInstance().getHgtFile(latLon);
     138                }
     139                else {
     140                    hgtFile = downloadedZipFile.toFile();
     141                }
     142            } catch (IOException e) {
     143                if (response != null)
     144                    Logging.error("Elevation: HGT server responded: " + response.getResponseCode() + " " + response.getResponseMessage());
     145                Logging.error("Elevation: Downloading HGT file " + hgtZipFileName + " failed: " + e.toString());
     146                downloadFailed();
     147                return;
     148            }
     149
     150            // This would happen, if the downloaded file was uncompressed, but it does not contain an appropriately named file (with HGT prefix)
     151            if (hgtFile == null) {
     152                Logging.error("Elevation: Downloaded compressed file " + hgtZipFileName + " did not contain a file with the expected HGT prefix!");
     153                downloadFailed();
     154                return;
     155            }
     156
     157            Logging.info("Elevation: Successfully downloaded HGT file " + hgtFile.getName() + " to HGT directory: " + HgtDownloader.this.hgtDirectory.toString());
     158            downloadSucceeded(hgtFile);
     159        }
     160
     161        private void downloading() {
     162            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
     163                listener.hgtFileDownloading(latLon);
     164        }
     165
     166        private void downloadSucceeded(File hgtFile) {
     167            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
     168                listener.hgtFileDownloadSucceeded(latLon, hgtFile);
     169        }
     170
     171        private void downloadFailed() {
     172            for (HgtDownloadListener listener : HgtDownloader.this.downloadListeners)
     173                listener.hgtFileDownloadFailed(latLon);
     174        }
     175    }
     176}
  • src/org/openstreetmap/josm/plugins/elevation/HgtFileImporter.java

    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 b public class HgtFileImporter extends FileImporter {  
    2525
    2626    @Override
    2727    public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
    28         Bounds bounds = HgtReader.read(file);
     28        Bounds bounds = HgtReader.getInstance().read(file);
    2929        if (bounds != null && MainApplication.getMap() != null && MainApplication.getMap().mapView != null)
    3030            MainApplication.getMap().mapView.zoomTo(bounds);
    3131    }
  • src/org/openstreetmap/josm/plugins/elevation/HgtReader.java

    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 b import java.io.File;  
    55import java.io.FileNotFoundException;
    66import java.io.IOException;
    77import java.io.InputStream;
     8import java.net.MalformedURLException;
    89import java.nio.ByteBuffer;
    910import java.nio.ByteOrder;
     11import java.nio.file.Files;
    1012import java.nio.file.Paths;
    11 import java.util.Arrays;
    1213import java.util.HashMap;
    13 import java.util.List;
    1414import java.util.Optional;
     15import java.util.Set;
    1516import java.util.regex.Matcher;
    1617import java.util.regex.Pattern;
     18import java.util.stream.Collectors;
     19import java.util.stream.Stream;
    1720
    1821import org.apache.commons.compress.utils.IOUtils;
    1922import org.openstreetmap.josm.data.Bounds;
    20 import org.openstreetmap.josm.data.Preferences;
    2123import org.openstreetmap.josm.data.coor.ILatLon;
    2224import org.openstreetmap.josm.io.Compression;
    2325import org.openstreetmap.josm.tools.CheckParameterUtil;
    2426import org.openstreetmap.josm.tools.Logging;
    2527
    2628/**
    27  *  Class HgtReader reads data from SRTM HGT files. Currently this class is restricted to a resolution of 3 arc seconds.
     29 *  Class {@code HgtReader} reads elevation data from SRTM HGT (Shuttle Radar Topography Mission Height) files.
     30 *  Currently this class is restricted to a resolution of 3 arc seconds (SRTM3).
     31 *
     32 *  SRTM3 HGT files are available at
     33 *  <a href="https://e4ftl01.cr.usgs.gov/MEASURES/SRTMGL3.003/2000.02.11/">NASA's Land Processes Distributed Active Archive Center (LP DAAC)</a>.
     34 *
     35 *  In order to access these files, registration at <a href="https://urs.earthdata.nasa.gov/users/new/">NASA Earthdata Login User Registration</a>
     36 *  and creating an authorization bearer token on this site are required.
    2837 *
    29  *  SRTM data files are available at the <a href="http://dds.cr.usgs.gov/srtm/version2_1/SRTM3">NASA SRTM site</a>
    3038 *  @author Oliver Wieland &lt;oliver.wieland@online.de&gt;
     39 *  @author Harald Hetzner
     40 *  @see HgtDownloader
    3141 */
    32 public class HgtReader {
     42public class HgtReader implements HgtDownloadListener {
    3343    private static final int SRTM_EXTENT = 1; // degree
    34     private static final List<String> COMPRESSION_EXT = Arrays.asList("xz", "gzip", "zip", "bz", "bz2");
    3544
    3645    public static final String HGT_EXT = ".hgt";
    3746
     47    public static final Pattern HGT_PREFIX_PATTERN = Pattern.compile("^(([NS])(\\d{2})([EW])(\\d{3})).+");
     48
    3849    // alter these values for different SRTM resolutions
    3950    public static final int HGT_VOID = Short.MIN_VALUE; // magic number which indicates 'void data' in HGT file
    4051
    41     private static final HashMap<String, short[][]> cache = new HashMap<>();
     52    private final HashMap<String, HgtCacheData> cache = new HashMap<>();
    4253
    43     public static double getElevationFromHgt(ILatLon coor) {
    44         try {
    45             String file = getHgtFileName(coor);
    46             // given area in cache?
    47             if (!cache.containsKey(file)) {
    48 
    49                 // fill initial cache value. If no file is found, then
    50                 // we use it as a marker to indicate 'file has been searched
    51                 // but is not there'
    52                 cache.put(file, null);
    53                 // Try all resource directories
    54                 for (String location : Preferences.getAllPossiblePreferenceDirs()) {
    55                     String fullPath = new File(location + File.separator + "elevation", file).getPath();
    56                     File f = new File(fullPath);
    57                     if (!f.exists()) {
    58                         for (String ext : COMPRESSION_EXT) {
    59                             f = new File(fullPath + "." + ext);
    60                             if (f.exists()) break;
    61                         }
     54    private static HgtReader hgtReader = null;
     55
     56    private File hgtDirectory = null;
     57    private boolean autoDownloadEnabled = false;
     58    private HgtDownloader hgtDownloader = null;
     59
     60
     61    public static HgtReader getInstance() {
     62        if (hgtReader == null)
     63            hgtReader = new HgtReader();
     64        return hgtReader;
     65    }
     66
     67    public static void destroyInstance() {
     68        hgtReader = null;
     69    }
     70
     71    private HgtReader() {
     72        this(ElevationPreferences.DEFAULT_HGT_DIRECTORY);
     73    }
     74
     75    private HgtReader(File hgtDirectory) {
     76        setHgtDirectory(hgtDirectory);
     77    }
     78
     79    public File getHgtDirectory() {
     80        return hgtDirectory;
     81    }
     82
     83    public void setHgtDirectory(File hgtDirectory) {
     84        if (!hgtDirectory.exists() && hgtDirectory.mkdirs())
     85            Logging.info("Elevation: Created directory for HGT files: " + hgtDirectory.toString());
     86        if (hgtDirectory.isDirectory()) {
     87            this.hgtDirectory = hgtDirectory;
     88            Logging.info("Elevation: Set directory for HGT files to: " + hgtDirectory.toString());
     89        }
     90        else {
     91            Logging.error("Elevation: Could not create directory for HGT files: " + hgtDirectory.toString());
     92            hgtDirectory = null;
     93        }
     94        if (hgtDownloader != null)
     95            hgtDownloader.setHgtDirectory(hgtDirectory);
     96    }
     97
     98    public void setAutoDownloadEnabled(boolean enabled) {
     99        if (autoDownloadEnabled == enabled)
     100            return;
     101        if (enabled) {
     102            if (hgtDirectory != null) {
     103                if (hgtDownloader == null)
     104                    try {
     105                        hgtDownloader = new HgtDownloader(hgtDirectory);
     106                        hgtDownloader.addDownloadListener(this);
     107                    } catch (MalformedURLException e) {
     108                        autoDownloadEnabled = false;
     109                        Logging.error("Elevation: Cannot enable auto-downloading: " + e.toString());
     110                        return;
    62111                    }
    63                     if (f.exists()) {
    64                         read(f);
    65                         break;
     112                else
     113                    hgtDownloader.setHgtDirectory(hgtDirectory);
     114                autoDownloadEnabled = true;
     115                Logging.info("Elevation: Enabled auto-downloading of HGT files to " + hgtDirectory.toString());
     116            }
     117            else {
     118                hgtDownloader = null;
     119                autoDownloadEnabled = false;
     120                Logging.error("Elevation: Cannot enable auto-downloading as directory for HGT files was not set");
     121            }
     122        }
     123        else {
     124            hgtDownloader = null;
     125            autoDownloadEnabled = false;
     126            Logging.info("Elevation: Disabled auto-downloading of HGT files");
     127        }
     128    }
     129
     130    /**
     131     * Returns the elevation at the location of the provided coordinate.
     132     * If there is not HGT file with elevation data for this location and
     133     * <code>autoDownload</code> is enabled, it will be attempted to download
     134     * the HGT file.
     135     *
     136     * @param latLon The location at which the elevation is of interest.
     137     * @return The elevation at the provided location or {@link ElevationHelper#NO_ELEVATION ElevationHelper.NO_ELEVATION}
     138     *         if no HGT file elevation data for the location is available at present.
     139     */
     140    public double getElevationFromHgt(ILatLon latLon) {
     141        String hgtPrefix = getHgtPrefix(latLon);
     142        HgtCacheData hgtCacheData;
     143
     144        synchronized(cache) {
     145            hgtCacheData = cache.get(hgtPrefix);
     146            // data not in cache
     147            if (hgtCacheData == null) {
     148                File hgtFile = getHgtFile(latLon);
     149                // If a HGT file with the data exists locally, read it in
     150                if (hgtFile != null) {
     151                    Logging.info("Elevation: Caching data for HGT prefix " + hgtPrefix + " from file " + hgtFile.getAbsolutePath());
     152                    short[][] data = null;
     153                    try {
     154                        data = readHgtFile(hgtFile.toString());
     155                    } catch (FileNotFoundException e) {
     156                        Logging.error("Elevation: Getting elevation from HGT file " + hgtFile.getAbsolutePath() + " failed: => " + e.getMessage());
     157                        // no problem... file not there
     158                        return ElevationHelper.NO_ELEVATION;
     159                    } catch (IOException ioe) {
     160                        // oops...
     161                        Logging.error(ioe);
     162                        // fallback
     163                        return ElevationHelper.NO_ELEVATION;
    66164                    }
     165                    hgtCacheData = new HgtCacheData(data);
     166                    cache.put(hgtPrefix, hgtCacheData);
     167                }
     168                // Otherwise, put an empty data set with status "missing" into the cache
     169                else {
     170                    hgtCacheData = new HgtCacheData();
     171                    cache.put(hgtPrefix, hgtCacheData);
    67172                }
    68173            }
    69 
    70             // read elevation value
    71             return readElevation(coor, file);
    72         } catch (FileNotFoundException e) {
    73             Logging.error("Get elevation from HGT " + coor + " failed: => " + e.getMessage());
    74             // no problem... file not there
    75             return ElevationHelper.NO_ELEVATION;
    76         } catch (Exception ioe) {
    77             // oops...
    78             Logging.error(ioe);
    79             // fallback
    80             return ElevationHelper.NO_ELEVATION;
     174            // Read elevation value if HGT data is available
     175            else if (hgtCacheData.getStatus() == HgtCacheData.Status.VALID) {
     176                return readElevationFromCache(latLon);
     177            }
     178            // If the HGT file with the relevant elevation data is missing and auto-downloading is enabled, try to download it
     179            else if (hgtCacheData.getStatus() == HgtCacheData.Status.MISSING && autoDownloadEnabled) {
     180                hgtCacheData.setDownloadScheduled();
     181                hgtDownloader.downloadHgtFile(latLon);
     182            }
    81183        }
     184        // If not valid elevation data could be returned, return no elevation
     185        return ElevationHelper.NO_ELEVATION;
    82186    }
    83187
    84     public static Bounds read(File file) throws IOException {
    85         String location = file.getName();
    86         for (String ext : COMPRESSION_EXT) {
    87             location = location.replaceAll("\\." + ext + "$", "");
    88         }
    89         short[][] sb = readHgtFile(file.getPath());
    90         // Overwrite the cache file (assume that is desired)
    91         cache.put(location, sb);
    92         Pattern pattern = Pattern.compile("([NS])(\\d{2})([EW])(\\d{3})");
    93         Matcher matcher = pattern.matcher(location);
    94         if (matcher.lookingAt()) {
    95             int lat = ("S".equals(matcher.group(1)) ? -1 : 1) * Integer.parseInt(matcher.group(2));
    96             int lon = ("W".equals(matcher.group(3)) ? -1 : 1) * Integer.parseInt(matcher.group(4));
     188    public Bounds read(File file) throws IOException {
     189        Matcher matcher = HGT_PREFIX_PATTERN.matcher(file.getName());
     190        if (matcher.matches()) {
     191            String hgtPrefix = matcher.group(1);
     192            short[][] data = readHgtFile(file.getPath());
     193            HgtCacheData hgtCacheData = new HgtCacheData(data);
     194            // Overwrite the cache file (assume that is desired)
     195            synchronized (cache) {
     196                cache.put(hgtPrefix, hgtCacheData);
     197            }
     198            int lat = ("S".equals(matcher.group(2)) ? -1 : 1) * Integer.parseInt(matcher.group(3));
     199            int lon = ("W".equals(matcher.group(4)) ? -1 : 1) * Integer.parseInt(matcher.group(5));
    97200            return new Bounds(lat, lon, lat + 1, lon + 1);
    98201        }
    99202        return null;
    100203    }
    101204
    102     private static short[][] readHgtFile(String file) throws IOException {
    103         CheckParameterUtil.ensureParameterNotNull(file);
     205    private static short[][] readHgtFile(String fileName) throws IOException {
     206        CheckParameterUtil.ensureParameterNotNull(fileName);
    104207
    105208        short[][] data = null;
    106209
    107         try (InputStream fis = Compression.getUncompressedFileInputStream(Paths.get(file))) {
     210        try (InputStream fis = Compression.getUncompressedFileInputStream(Paths.get(fileName))) {
    108211            // choose the right endianness
    109212            ByteBuffer bb = ByteBuffer.wrap(IOUtils.toByteArray(fis));
    110213            //System.out.println(Arrays.toString(bb.array()));
    public class HgtReader {  
    130233     * Reads the elevation value for the given coordinate.
    131234     *
    132235     * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>
    133      * @param coor the coordinate to get the elevation data for
     236     * @param latLon the coordinate to get the elevation data for
    134237     * @return the elevation value or <code>Double.NaN</code>, if no value is present
    135238     */
    136     public static double readElevation(ILatLon coor) {
    137         String tag = getHgtFileName(coor);
    138         return readElevation(coor, tag);
    139     }
     239    private double readElevationFromCache(ILatLon latLon) {
     240        String hgtPrefix = getHgtPrefix(latLon);
     241        HgtCacheData hgtCacheData;
    140242
    141     /**
    142      * Reads the elevation value for the given coordinate.
    143      *
    144      * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>
    145      * @param coor the coordinate to get the elevation data for
    146      * @param fileName The expected filename
    147      * @return the elevation value or <code>Double.NaN</code>, if no value is present
    148      */
    149     public static double readElevation(ILatLon coor, String fileName) {
     243        synchronized(cache) {
     244            hgtCacheData = cache.get(hgtPrefix);
     245        }
    150246
    151         short[][] sb = cache.get(fileName);
     247        if (hgtCacheData == null || hgtCacheData.getStatus() != HgtCacheData.Status.VALID)
     248            return ElevationHelper.NO_ELEVATION;
    152249
    153         if (sb == null) {
     250        short[][] data = hgtCacheData.getData();
     251        if (data == null)
    154252            return ElevationHelper.NO_ELEVATION;
    155         }
    156253
    157         int[] index = getIndex(coor, sb.length);
    158         short ele = sb[index[0]][index[1]];
     254        int[] index = getIndex(latLon, data.length);
     255        short ele;
    159256
    160         if (ele == HGT_VOID) {
     257        try {
     258            ele = data[index[0]][index[1]];
     259        } catch (ArrayIndexOutOfBoundsException e) {
     260            Logging.error("Elevation: Error reading elevation data for prefix " + hgtPrefix + ":" + e.toString());
    161261            return ElevationHelper.NO_ELEVATION;
    162262        }
     263
     264        if (ele == HGT_VOID)
     265            return ElevationHelper.NO_ELEVATION;
    163266        return ele;
    164267    }
    165268
    166     public static Optional<Bounds> getBounds(ILatLon location) {
    167         final String fileName = getHgtFileName(location);
    168         final short[][] sb = cache.get(fileName);
     269    public Optional<Bounds> getBounds(ILatLon location) {
     270        String hgtPrefix = getHgtPrefix(location);
     271        short[][] data = null;
     272
     273        synchronized(cache) {
     274            data = cache.get(hgtPrefix).getData();
     275        }
    169276
    170         if (sb == null) {
     277        if (data == null) {
    171278            return Optional.empty();
    172279        }
    173280
    174281        final double latDegrees = location.lat();
    175282        final double lonDegrees = location.lon();
    176283
    177         final float fraction = ((float) SRTM_EXTENT) / sb.length;
     284        final float fraction = ((float) SRTM_EXTENT) / data.length;
    178285        final int latitude = (int) Math.floor(latDegrees) + (latDegrees < 0 ? 1 : 0);
    179286        final int longitude = (int) Math.floor(lonDegrees) + (lonDegrees < 0 ? 1 : 0);
    180287
    181         final int[] index = getIndex(location, sb.length);
     288        final int[] index = getIndex(location, data.length);
    182289        final int latSign = latitude > 0 ? 1 : -1;
    183290        final int lonSign = longitude > 0 ? 1 : -1;
    184291        final double minLat = latitude + latSign * fraction * index[0];
    public class HgtReader {  
    217324        return new int[] { latitude, longitude };
    218325    }
    219326
     327    /**
     328     * Returns the HGT file, which contains elevation data for a given location, found in the given HGT directory.
     329     * Note: This method only checks if there is a file name with appropriate name. It does not check if the file content is appropriate.
     330     * @param latLon The location for which the SRTM3 HGT file with the elevation data is of interest.
     331     * @return The HGT file with the elevation data for the given location or <code>null</code>
     332     *         if there is no file with an appropriate name.
     333     */
     334    public File getHgtFile(ILatLon latLon) {
     335        String hgtPrefixFromLatLon = getHgtPrefix(latLon);
     336        // https://www.baeldung.com/java-list-directory-files
     337        Set<File> files = Stream.of(hgtDirectory.listFiles()).filter(file -> !file.isDirectory()).collect(Collectors.toSet());
     338        for (File file : files) {
     339            String hgtPrefixFromFile = getHgtPrefix(file.getName());
     340            if (hgtPrefixFromFile != null && hgtPrefixFromFile.equals(hgtPrefixFromLatLon))
     341                return file;
     342        }
     343
     344        return null;
     345    }
     346
    220347    /**
    221348     * Gets the associated HGT file name for the given way point. Usually the
    222349     * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude
    public class HgtReader {  
    225352     * @param latLon the coordinate to get the filename for
    226353     * @return the file name of the HGT file
    227354     */
    228     public static String getHgtFileName(ILatLon latLon) {
     355    /*public static String getHgtFileName(ILatLon latLon) {
     356        return getHgtPrefix(latLon) + HGT_EXT;
     357    }*/
     358
     359    public static String getHgtPrefix(ILatLon latLon) {
    229360        int lat = (int) Math.floor(latLon.lat());
    230361        int lon = (int) Math.floor(latLon.lon());
    231362
    public class HgtReader {  
    241372            lon = Math.abs(lon);
    242373        }
    243374
    244         return String.format("%s%2d%s%03d" + HGT_EXT, latPref, lat, lonPref, lon);
     375        return String.format("%s%2d%s%03d", latPref, lat, lonPref, lon);
     376    }
     377
     378    public static String getHgtPrefix(String fileName) {
     379        Matcher matcher = HGT_PREFIX_PATTERN.matcher(fileName);
     380        if (matcher.matches())
     381            return matcher.group(1);
     382        return null;
    245383    }
    246384
    247385    public static double frac(double d) {
    public class HgtReader {  
    254392        return fPart;
    255393    }
    256394
    257     public static void clearCache() {
    258         cache.clear();
     395    public void clearCache() {
     396        synchronized(cache) {
     397            cache.clear();
     398        }
     399    }
     400
     401    @Override
     402    public void hgtFileDownloading(ILatLon latLon) {
     403        String hgtPrefix = getHgtPrefix(latLon);
     404
     405        synchronized (cache) {
     406            HgtCacheData hgtCacheData = cache.get(hgtPrefix);
     407            // Should not happen
     408            if (hgtCacheData == null) {
     409                hgtCacheData = new HgtCacheData();
     410                cache.put(hgtPrefix, hgtCacheData);
     411            }
     412            hgtCacheData.setDownloading();
     413        }
     414    }
     415
     416    @Override
     417    public void hgtFileDownloadSucceeded(ILatLon latLon, File hgtFile) {
     418        short[][] data = null;
     419        try {
     420            data = readHgtFile(hgtFile.getAbsolutePath());
     421        } catch (Exception e) {
     422            Logging.error("Elevation: Error reading HGT file " + hgtFile.getAbsolutePath() + ": " + e.toString());
     423        }
     424
     425        String hgtPrefix = getHgtPrefix(latLon);
     426        HgtCacheData hgtCacheData;
     427        synchronized (cache) {
     428            hgtCacheData = cache.get(hgtPrefix);
     429            if (hgtCacheData != null)
     430                hgtCacheData.setData(data);
     431            // Should not happen
     432            else {
     433                hgtCacheData = new HgtCacheData(data);
     434                cache.put(hgtPrefix, hgtCacheData);
     435            }
     436        }
     437
     438        // In case that the downloaded file is corrupt, try to delete it
     439        if (data == null) {
     440            hgtCacheData.setDownloadFailed();
     441            Logging.info("Elevation: Deleting downloaded, but corrupt HGT file: " + hgtFile.getAbsolutePath());
     442            try {
     443                Files.delete(Paths.get(hgtFile.getAbsolutePath()));
     444            } catch (IOException e) {
     445                Logging.error("Elevation: Error deleting downloaded, but corrupt HGT file: " + e.toString());
     446            }
     447        }
     448    }
     449
     450    @Override
     451    public void hgtFileDownloadFailed(ILatLon latLon) {
     452        String hgtPrefix = getHgtPrefix(latLon);
     453
     454        synchronized (cache) {
     455            HgtCacheData hgtCacheData = cache.get(hgtPrefix);
     456            // Should not happen
     457            if (hgtCacheData == null) {
     458                hgtCacheData = new HgtCacheData();
     459                cache.put(hgtPrefix, hgtCacheData);
     460            }
     461            hgtCacheData.setDownloadFailed();
     462        }
     463
     464    }
     465
     466    private static class HgtCacheData {
     467
     468        private short[][] data;
     469        private Status status;
     470
     471        enum Status {
     472            VALID,
     473            MISSING,
     474            DOWNLOAD_SCHEDULED,
     475            DOWNLOADING,
     476            DOWNLOAD_FAILED
     477        }
     478
     479        public HgtCacheData() {
     480            this(null);
     481        }
     482
     483        public HgtCacheData(short[][] data) {
     484            setData(data);
     485        }
     486
     487        public short[][] getData() {
     488            return data;
     489        }
     490
     491        public Status getStatus() {
     492            return status;
     493        }
     494
     495        public void setDownloadScheduled() {
     496            status = Status.DOWNLOAD_SCHEDULED;
     497        }
     498
     499        public void setDownloading() {
     500            status = Status.DOWNLOADING;
     501        }
     502
     503        public void setDownloadFailed() {
     504            status = Status.DOWNLOAD_FAILED;
     505            data = null;
     506        }
     507
     508        public void setData(short[][] data) {
     509            if (data == null)
     510                status = Status.MISSING;
     511            else
     512                status = Status.VALID;
     513            this.data = data;
     514        }
    259515    }
    260516}
  • src/org/openstreetmap/josm/plugins/elevation/grid/ElevationGridLayer.java

    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 b import org.openstreetmap.josm.gui.Notification;  
    3434import org.openstreetmap.josm.gui.layer.Layer;
    3535import org.openstreetmap.josm.gui.util.GuiHelper;
    3636import org.openstreetmap.josm.plugins.elevation.ElevationHelper;
    37 import org.openstreetmap.josm.plugins.elevation.HgtReader;
    3837import org.openstreetmap.josm.plugins.elevation.IVertexRenderer;
    3938import org.openstreetmap.josm.tools.ImageProvider;
    4039import org.openstreetmap.josm.tools.Logging;
    public class ElevationGridLayer extends Layer implements TileLoaderListener, Mou  
    5857
    5958    public ElevationGridLayer(String name) {
    6059        super(name);
    61         HgtReader.clearCache();
    6260        MainApplication.getMap().mapView.addMouseListener(this);
    6361
    6462        setOpacity(0.8);
    public class ElevationGridLayer extends Layer implements TileLoaderListener, Mou  
    283281    }
    284282
    285283    @Override
    286     public void destroy() {
    287         super.destroy();
    288         HgtReader.clearCache();
     284    public void finalize() throws Throwable {
     285        super.finalize();
    289286        MainApplication.getMap().mapView.removeMouseListener(this);
    290287    }
    291288}
  • new file src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreference.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.plugins.elevation.gui;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import javax.swing.Box;
     7
     8import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
     9import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     10import org.openstreetmap.josm.plugins.elevation.ElevationPreferences;
     11import org.openstreetmap.josm.plugins.elevation.ElevationProfilePlugin;
     12import org.openstreetmap.josm.spi.preferences.Config;
     13import org.openstreetmap.josm.tools.GBC;
     14
     15/**
     16 * Elevation data sub-preferences in preferences.
     17 * @author Harald Hetzner
     18 *
     19 */
     20public final class ElevationPreference extends DefaultTabPreferenceSetting {
     21
     22    private ElevationPreferencesPanel pnlHgtPreferences;
     23
     24    public ElevationPreference() {
     25        super("elevation", tr("Elevation Data"), tr("Elevation preferences and connection settings for the HGT server."));
     26    }
     27
     28    @Override
     29    public void addGui(PreferenceTabbedPane gui) {
     30        pnlHgtPreferences = new ElevationPreferencesPanel();
     31        pnlHgtPreferences.add(Box.createVerticalGlue(), GBC.eol().fill());
     32        gui.createPreferenceTab(this).add(pnlHgtPreferences, GBC.eol().fill());
     33    }
     34
     35    /**
     36     * Saves the values to the preferences and applies them.
     37     */
     38    @Override
     39    public boolean ok() {
     40        // Save to preferences file
     41        pnlHgtPreferences.saveToPreferences();
     42
     43        // Apply preferences
     44        boolean elevationEnabled = Config.getPref().getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED);
     45        ElevationProfilePlugin.getInstance().setElevationEnabled(elevationEnabled);
     46
     47        return false;
     48    }
     49
     50    @Override
     51    public String getHelpContext() {
     52        return null;
     53    }
     54}
  • new file src/org/openstreetmap/josm/plugins/elevation/gui/ElevationPreferencesPanel.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.plugins.elevation.gui;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Desktop;
     7import java.awt.Dimension;
     8import java.awt.GridBagConstraints;
     9import java.awt.GridBagLayout;
     10import java.awt.Insets;
     11import java.io.IOException;
     12import java.net.URI;
     13
     14import javax.swing.BorderFactory;
     15import javax.swing.JCheckBox;
     16import javax.swing.JLabel;
     17import javax.swing.JPanel;
     18import javax.swing.event.HyperlinkEvent;
     19
     20import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
     21import org.openstreetmap.josm.gui.widgets.JosmTextField;
     22import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
     23import org.openstreetmap.josm.plugins.elevation.ElevationPreferences;
     24import org.openstreetmap.josm.spi.preferences.Config;
     25import org.openstreetmap.josm.spi.preferences.IPreferences;
     26import org.openstreetmap.josm.tools.GBC;
     27import org.openstreetmap.josm.tools.Logging;
     28
     29/**
     30 * Component allowing input of HGT (elevation) server settings.
     31 */
     32public class ElevationPreferencesPanel extends VerticallyScrollablePanel {
     33
     34    static final class AutoSizePanel extends JPanel {
     35        AutoSizePanel() {
     36            super(new GridBagLayout());
     37        }
     38
     39        @Override
     40        public Dimension getMinimumSize() {
     41            return getPreferredSize();
     42        }
     43    }
     44
     45    private final JCheckBox cbEnableElevation = new JCheckBox(tr("Enable Use of Elevation Data"));
     46    private final JMultilineLabel lblpElevationData =
     47            new JMultilineLabel(tr("<html>STRM3 HGT files can be downloaded from <a href=\"{0}\">{0}</a>.</html>", ElevationPreferences.HGT_SERVER_BASE_URL));
     48    private final JCheckBox cbEnableElevationProfile = new JCheckBox(tr("Enable Elevation Profile Layer"));
     49    private final JCheckBox cbEnableAutoDownload = new JCheckBox(tr("Enable Automatic Downloading of Elevation Data"));
     50    private final JLabel lblAuthBearer = new JLabel(tr("Authorization Bearer Token:"));
     51    private final JosmTextField tfAuthBearer = new JosmTextField();
     52    private final JMultilineLabel lblAuthBearerNotes =
     53            new JMultilineLabel(tr("<html>You need to register at <a href=\"{0}\">{0}</a> to create the authorization bearer token.</html>",
     54                    ElevationPreferences.HGT_SERVER_REGISTRATION_URL));
     55
     56    /**
     57     * Builds the panel for the elevation preferences.
     58     *
     59     * @return Panel with elevation preferences
     60     */
     61    protected final JPanel buildHgtPreferencesPanel() {
     62        cbEnableElevation.setToolTipText(tr("STRM3 HGT files need to be placed in {0}", ElevationPreferences.DEFAULT_HGT_DIRECTORY.getAbsolutePath()));
     63        cbEnableElevation.addItemListener(event -> updateEnabledState());
     64
     65        lblpElevationData.setEditable(false);
     66        lblpElevationData.addHyperlinkListener(event -> browseHyperlink(event));
     67
     68        cbEnableElevationProfile.addItemListener(event -> updateEnabledState());
     69
     70        cbEnableAutoDownload.setToolTipText(tr("STRM3 HGT files will be downloaded from {0}", ElevationPreferences.HGT_SERVER_BASE_URL));
     71        cbEnableAutoDownload.addItemListener(event -> updateEnabledState());
     72
     73        lblAuthBearerNotes.setEditable(false);
     74        lblAuthBearerNotes.addHyperlinkListener(event -> browseHyperlink(event));
     75
     76        JPanel pnl = new AutoSizePanel();
     77        GridBagConstraints gc = new GridBagConstraints();
     78
     79        gc.anchor = GridBagConstraints.LINE_START;
     80        gc.insets = new Insets(5, 5, 0, 0);
     81        gc.fill = GridBagConstraints.HORIZONTAL;
     82        gc.gridwidth = 2;
     83        gc.weightx = 1.0;
     84        pnl.add(cbEnableElevation, gc);
     85
     86        gc.gridy = 1;
     87        pnl.add(lblpElevationData, gc);
     88
     89        gc.gridy = 2;
     90        pnl.add(cbEnableElevationProfile, gc);
     91
     92        gc.gridy = 3;
     93        pnl.add(cbEnableAutoDownload, gc);
     94
     95        gc.gridy = 4;
     96        gc.fill = GridBagConstraints.NONE;
     97        gc.gridwidth = 1;
     98        gc.weightx = 0.0;
     99        pnl.add(lblAuthBearer, gc);
     100
     101        gc.gridx = 1;
     102        gc.fill = GridBagConstraints.HORIZONTAL;
     103        gc.weightx = 1.0;
     104        pnl.add(tfAuthBearer, gc);
     105
     106        gc.gridy = 5;
     107        gc.gridx = 0;
     108        gc.gridwidth = 2;
     109        gc.weightx = 1.0;
     110        pnl.add(lblAuthBearerNotes, gc);
     111
     112        // add an extra spacer, otherwise the layout is broken
     113        gc.gridy = 6;
     114        gc.gridwidth = 2;
     115        gc.fill = GridBagConstraints.BOTH;
     116        gc.weighty = 1.0;
     117        pnl.add(new JPanel(), gc);
     118        return pnl;
     119    }
     120
     121
     122    /**
     123     * Initializes the panel with the values from the preferences.
     124     */
     125    public final void initFromPreferences() {
     126        IPreferences pref = Config.getPref();
     127
     128        cbEnableElevation.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_ENABLED));
     129        cbEnableElevationProfile.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_PROFILE_ENABLED));
     130        cbEnableAutoDownload.setSelected(pref.getBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED, ElevationPreferences.DEFAULT_ELEVATION_AUTO_DOWNLOAD_ENABLED));
     131        tfAuthBearer.setText(pref.get(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, ElevationPreferences.DEFAULT_ELEVATION_SERVER_AUTH_BEARER));
     132    }
     133
     134    private final void updateEnabledState() {
     135        if (cbEnableElevation.isSelected()) {
     136            lblpElevationData.setEnabled(true);
     137            cbEnableElevationProfile.setEnabled(true);
     138            cbEnableAutoDownload.setEnabled(true);
     139            lblAuthBearer.setEnabled(cbEnableAutoDownload.isSelected());
     140            tfAuthBearer.setEnabled(cbEnableAutoDownload.isSelected());
     141            lblAuthBearerNotes.setEnabled(cbEnableAutoDownload.isSelected());
     142        }
     143        else {
     144            lblpElevationData.setEnabled(false);
     145            cbEnableElevationProfile.setEnabled(false);
     146            cbEnableAutoDownload.setEnabled(false);
     147            lblAuthBearer.setEnabled(false);
     148            tfAuthBearer.setEnabled(false);
     149            lblAuthBearerNotes.setEnabled(false);
     150        }
     151    }
     152
     153    // https://stackoverflow.com/questions/14101000/hyperlink-to-open-in-browser-in-java
     154    // https://www.codejava.net/java-se/swing/how-to-create-hyperlink-with-jlabel-in-java-swing
     155    private final void browseHyperlink(HyperlinkEvent event) {
     156            if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
     157                String url = event.getURL().toString();
     158                try {
     159                    Desktop.getDesktop().browse(URI.create(url));
     160                } catch (IOException e) {
     161                    Logging.error(e.toString());
     162                }
     163            }
     164    }
     165
     166    /**
     167     * Constructs a new {@code HgtPreferencesPanel}.
     168     */
     169    public ElevationPreferencesPanel() {
     170        setLayout(new GridBagLayout());
     171        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
     172        add(buildHgtPreferencesPanel(), GBC.eop().anchor(GridBagConstraints.NORTHWEST).fill(GridBagConstraints.BOTH));
     173
     174        initFromPreferences();
     175        updateEnabledState();
     176    }
     177
     178    /**
     179     * Saves the current values to the preferences
     180     */
     181    public void saveToPreferences() {
     182        IPreferences pref = Config.getPref();
     183        pref.putBoolean(ElevationPreferences.ELEVATION_ENABLED, cbEnableElevation.isSelected());
     184        pref.putBoolean(ElevationPreferences.ELEVATION_PROFILE_ENABLED, cbEnableElevationProfile.isSelected());
     185        pref.putBoolean(ElevationPreferences.ELEVATION_AUTO_DOWNLOAD_ENABLED, cbEnableAutoDownload.isSelected());
     186        pref.put(ElevationPreferences.ELEVATION_SERVER_AUTH_BEARER, tfAuthBearer.getText());
     187    }
     188}
  • new file src/org/openstreetmap/josm/plugins/elevation/gui/LocalElevationLabel.java

    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
    - +  
     1package org.openstreetmap.josm.plugins.elevation.gui;
     2
     3import static org.openstreetmap.josm.tools.I18n.tr;
     4
     5import java.awt.event.MouseEvent;
     6import java.awt.event.MouseMotionListener;
     7import java.text.DecimalFormat;
     8
     9import org.openstreetmap.josm.data.coor.ILatLon;
     10import org.openstreetmap.josm.gui.MapFrame;
     11import org.openstreetmap.josm.gui.MapStatus;
     12import org.openstreetmap.josm.gui.widgets.ImageLabel;
     13import org.openstreetmap.josm.plugins.elevation.ElevationHelper;
     14import org.openstreetmap.josm.plugins.elevation.HgtReader;
     15import org.openstreetmap.josm.tools.GBC;
     16
     17public class LocalElevationLabel extends ImageLabel implements MouseMotionListener {
     18
     19        private final MapFrame mapFrame;
     20
     21        private final DecimalFormat ELEVATION_FORMAT = new DecimalFormat("0 m");
     22
     23    public LocalElevationLabel(MapFrame mapFrame) {
     24        super("ele", tr("The terrain elevation at the mouse pointer."), 10, MapStatus.PROP_BACKGROUND_COLOR.get());
     25
     26        mapFrame.mapView.addMouseMotionListener(this);
     27
     28        setForeground(MapStatus.PROP_FOREGROUND_COLOR.get());
     29        // Add after the longitude ImageLabel at index = 2
     30        // or at index 0 or 1, if index = 2 should be out of range
     31        int index = Math.min(mapFrame.statusLine.getComponentCount(), 2);
     32        mapFrame.statusLine.add(this, GBC.std().insets(3, 0, 0, 0), index);
     33
     34        this.mapFrame = mapFrame;
     35    }
     36
     37    private void updateEleText(ILatLon coor) {
     38        double ele = HgtReader.getInstance().getElevationFromHgt(coor);
     39        if (ElevationHelper.isValidElevation(ele))
     40            setText(ELEVATION_FORMAT.format(ele));
     41        else
     42            setText(tr("No data"));
     43    }
     44
     45        public void destroy() {
     46                mapFrame.mapView.removeMouseMotionListener(this);
     47        }
     48
     49        @Override
     50        public void mouseDragged(MouseEvent e) {
     51                mouseMoved(e);
     52        }
     53
     54        @Override
     55        public void mouseMoved(MouseEvent e) {
     56                if (mapFrame.mapView.getCenter() == null)
     57            return;
     58        // Do not update the view if ctrl or right button is pressed.
     59        if ((e.getModifiersEx() & (MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0)
     60            updateEleText(mapFrame.mapView.getLatLon(e.getX(), e.getY()));
     61
     62        }
     63}